回顾最开始的内容不难想到,我们需要封装一些游戏组件以便于在各类场景中加载和管理。SpriteKit框架下我们只需要实现SKNode的子类,或者NSObject的子类来,最终把需要的节点以合适的时机通过addChild的形式加入到SKScene场景中。
SKTexture *terrainTexture = [SKTexture textureWithImageNamed:@"terrain"];
SKSpriteNode *node1 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];`
地板组件的实现代码如下:
#import "LJZTerrain.h"
#import "LJZConstants.h"
@implementation LJZTerrain
+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
SKTexture *terrainTexture;
if(type == 1){
terrainTexture = [SKTexture textureWithImageNamed:@"terrain"];
} else if(type == 2){
terrainTexture = [SKTexture textureWithImageNamed:@"terrain_2"];
} else {
terrainTexture = [SKTexture textureWithImageNamed:@"terrain_3"];
}
SKSpriteNode *node1 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];
node1.anchorPoint = CGPointMake(0, 1);
node1.position = CGPointMake(0, 0);
SKSpriteNode *node2 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];
node2.anchorPoint = CGPointMake(0, 1);
node2.position = CGPointMake(320, 0);
CGSize size = CGSizeMake(640, 60);
SKSpriteNode *terrain = [SKSpriteNode spriteNodeWithTexture:terrainTexture size:size];
terrain.zPosition = 1;
CGPoint location = CGPointMake(0.0f, 60);
terrain.anchorPoint = CGPointMake(0, 1);
terrain.position = location;
[terrain addChild:node1];
[terrain addChild:node2];
[parentNode addChild:terrain];
SKNode *terrainBody = [SKNode node];
terrainBody.position = CGPointMake(160.0f, 35);;
terrainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(320, 20)];
terrainBody.physicsBody.dynamic = NO;
terrainBody.physicsBody.affectedByGravity = NO;
terrainBody.physicsBody.collisionBitMask = 0;
terrainBody.physicsBody.categoryBitMask = terrainType;
terrainBody.physicsBody.contactTestBitMask = heroType;
[parentNode addChild:terrainBody];
[terrain runAction:[SKAction repeatActionForever:
[SKAction sequence:@[
[SKAction moveToX:-320 duration:5.0f],
[SKAction moveToX:0 duration:.0f]
]]]
];
}
@end
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface LJZPipe : NSObject
+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type;
@end
NS_ASSUME_NONNULL_END
+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
CGFloat offset = 660;
CGFloat startY = 10.0f + (CGFloat)arc4random_uniform(4) * 70.0f;
[parentNode addChild:[self createPipeAtY:(startY + offset) isTopPipe:YES withType:type]];
[parentNode addChild:[self createPipeAtY:startY isTopPipe:NO withType:type]];
}
#import "LJZPipe.h"
#import "LJZConstants.h"
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
static const CGFloat finalPosition = -50;
static const CGFloat duration = 6.0;
@implementation LJZPipe
+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
CGFloat offset = 660;
CGFloat startY = 10.0f + (CGFloat)arc4random_uniform(4) * 70.0f;
[parentNode addChild:[self createPipeAtY:(startY + offset) isTopPipe:YES withType:type]];
[parentNode addChild:[self createPipeAtY:startY isTopPipe:NO withType:type]];
}
+ (id)createPipeAtY:(CGFloat)startY isTopPipe:(BOOL)isTopPipe withType:(NSInteger)type
{
SKSpriteNode *pipeNode;
if (type == 1){
pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe"];
}else if(type == 2 ){
pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe_2"];
}else{
pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe_3"];
}
pipeNode.position = CGPointMake(ScreenWidth + pipeNode.size.width, startY);
pipeNode.yScale = (isTopPipe) ? 1.0f : -1.0f;
pipeNode.zPosition = 0;
pipeNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:pipeNode.size];
pipeNode.physicsBody.dynamic = NO;
pipeNode.physicsBody.collisionBitMask = heroType;
pipeNode.physicsBody.categoryBitMask = pipeType;
pipeNode.physicsBody.contactTestBitMask = heroType;
[self animate:pipeNode];
return pipeNode;
}
+ (void)animate:(SKNode *)node
{
[node runAction:
[SKAction sequence:@[
[SKAction moveToX:finalPosition duration:duration],
[SKAction removeFromParent]
]
]
];
}
@end
#import
#import
@interface LJZToolOne : NSObject
- (void)addNewNodeTo:(SKNode *)parentNode;
- (void)FinishBitAnimate;
@end
#import "LJZToolOne.h"
#import "LJZConstants.h"
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
static const CGFloat finalPosition = -50;
static const CGFloat duration = 6.0;
@interface LJZToolOne()
@property (nonatomic, strong)SKSpriteNode *toolNode;
@end
@implementation LJZToolOne
- (void)addNewNodeTo:(SKNode *)parentNode;
{
CGFloat startY = 100.0f + (CGFloat)arc4random_uniform(8) * 70.0f;
[parentNode addChild:[self createPipeAtY:startY]];
}
- (id)createPipeAtY:(CGFloat)startY
{
self.toolNode = [SKSpriteNode spriteNodeWithImageNamed:@"tool_100"];
_toolNode.position = CGPointMake(ScreenWidth + _toolNode.size.width, startY);
_toolNode.zPosition = 0;
_toolNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_toolNode.size];
_toolNode.physicsBody.dynamic = NO;
_toolNode.physicsBody.collisionBitMask = heroType;
_toolNode.physicsBody.categoryBitMask = toolTypeOne;
_toolNode.physicsBody.contactTestBitMask = heroType;
[self animate:_toolNode];
return _toolNode;
}
- (void)animate:(SKNode *)node
{
[node runAction:
[SKAction sequence:@[
[SKAction moveToX:finalPosition duration:duration],
[SKAction removeFromParent]
]
]
];
}
- (void)FinishBitAnimate
{
[self.toolNode runAction:[SKAction removeFromParent]];
}
@end
#import
@interface LJZHero : SKSpriteNode
+ (id)createNodeOn:(SKNode *)parent;
+ (id)createSpriteOn:(SKNode *)parent;
- (void)update;
- (void)goDown;
- (void)flap;
@end
- (void)animate
{
NSArray *animationFrames = @[
[SKTexture textureWithImageNamed:@"hero1"],
[SKTexture textureWithImageNamed:@"hero2"],
[SKTexture textureWithImageNamed:@"hero3"],
];
[self runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:animationFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:@"flyingHero"];
}
- (id)init
{
self = [super initWithImageNamed:@"hero1"];
if (self) {
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:
CGSizeMake(self.size.width * .95f,
self.size.height * .95f)];
self.physicsBody.dynamic = YES;
self.physicsBody.collisionBitMask = pipeType;
self.physicsBody.categoryBitMask = heroType;
[self animate];
}
return self;
}
+ (id)createNodeOn:(SKNode *)parent
{
id hero = [LJZHero new];
[parent addChild:hero];
return hero;
}
+ (id)createSpriteOn:(SKNode *)parent
{
SKNode *hero = [self createNodeOn:parent];
hero.physicsBody = nil;
return hero;
}
- (void)update
{
if (self.physicsBody.velocity.dy > 30.0) {
self.zRotation = (CGFloat) M_PI/ 6.0f;
} else if (self.physicsBody.velocity.dy < -100.0) {
self.zRotation = -(CGFloat) M_PI/ 4.0f;
} else {
self.zRotation = 0.0f;
}
}
- (void)goDown
{
[self.physicsBody applyImpulse:CGVectorMake(0, -20)];
}
- (void)flap
{
self.physicsBody.velocity = CGVectorMake(0, 0);
[self.physicsBody applyImpulse:CGVectorMake(0, 10)];
}
@interface LJZGameScene () <SKPhysicsContactDelegate>
@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;
@property (nonatomic, strong) SKAction *getScoreSound;
@property (nonatomic, strong) SKAction *ggSound;
@property (nonatomic, strong) SKLabelNode *scoreLabel;
@property (nonatomic, assign) NSInteger score;
@property (nonatomic, strong) LJZHero *hero;
@property (nonatomic, strong) NSTimer *pipeTimer;
@property (nonatomic, strong) NSTimer *toolTimerOne;
@property (nonatomic, strong) NSTimer *toolTimerTwo;
@property (nonatomic, strong) NSTimer *toolTimerThree;
@property (nonatomic, strong) LJZToolOne *toolOfOne;
@property (nonatomic, strong) LJZToolTwo *toolOfTwo;
@property (nonatomic, strong) LJZToolThree *toolOfThree;
@end
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
[self ApplyMusic];
[self setup];
}
- (void)ApplyMusic
{
NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"round1_bgm" ofType:@"mp3"];
self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:NULL];
self.bgmPlayer.numberOfLoops = -1;
[self.bgmPlayer play];
}
- (void)setup
{
[self preloadSounds];
[self createWorld];
[self createScoreLabel];
[self createHero];
[self createTerrain];
[self schedulePipe];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self scheduleTool];
});
}
- (void)die
{
[self.pipeTimer invalidate];
[self.toolTimerOne invalidate];
[self.toolTimerTwo invalidate];
[self.toolTimerThree invalidate];
[self.bgmPlayer stop];
self.bgmPlayer = nil;
[self changeToResultSceneWithScore:self.score];
}
- (void)changeToResultSceneWithScore:(NSUInteger)gameScore
{
LJZResultScene *resultScene = [[LJZResultScene alloc] initWithSize:self.size score:gameScore];
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
[self.scene.view presentScene:resultScene transition:reveal];
}
- (void)changeToNextGameSceneWithScore:(NSUInteger)gameScore
{
[self.pipeTimer invalidate];
[self.toolTimerOne invalidate];
[self.toolTimerTwo invalidate];
[self.toolTimerThree invalidate];
[self.bgmPlayer stop];
self.bgmPlayer = nil;
LJZGameSceneTwo *nextScene = [[LJZGameSceneTwo alloc] initWithSize:self.size score:gameScore];
SKTransition *reveal = [SKTransition doorwayWithDuration:1.0];
[self.scene.view presentScene:nextScene transition:reveal];
}
typedef NS_OPTIONS(NSUInteger, CollisionCategory) {
heroType = (1 << 0),
terrainType = (1 << 1),
pipeType = (1 << 2),
toolTypeOne = (1 << 3),
toolTypeTwo = (1 << 4),
toolTypeThree = (1 << 5)
};
接着实现场景遵守的SKPhysicsContactDelegate中的方法:contact参数表示两个物理体之间的联系,通过或运算判断是哪些物理体之间发生了碰撞。
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (heroType | pipeType)) {
[self.hero goDown];
[self runAction:self.ggSound completion:^{
[self die];
}];
} else if (collision == (heroType | terrainType)) {
[self runAction:self.ggSound completion:^{
[self die];
}];
} else if (collision == (heroType | toolTypeOne)) {
[self.toolOfOne FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore100];
}];
} else if (collision == (heroType | toolTypeTwo)) {
[self.toolOfTwo FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore500];
}];
} else if (collision == (heroType | toolTypeThree)) {
[self.toolOfThree FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore1000];
}];
}
}
完整的游戏场景实现文件内容如下:
#import "LJZGameScene.h"
#import "LJZGameSceneTwo.h"
#import "LJZMenuScene.h"
#import "LJZResultScene.h"
#import "LJZConstants.h"
#import "LJZTerrain.h"
#import "LJZHero.h"
#import "LJZPipe.h"
#import "LJZToolOne.h"
#import "LJZToolTwo.h"
#import "LJZToolThree.h"
#import
@interface LJZGameScene () <SKPhysicsContactDelegate>
@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;
@property (nonatomic, strong) SKAction *getScoreSound;
@property (nonatomic, strong) SKAction *ggSound;
@property (nonatomic, strong) SKLabelNode *scoreLabel;
@property (nonatomic, assign) NSInteger score;
@property (nonatomic, strong) LJZHero *hero;
@property (nonatomic, strong) NSTimer *pipeTimer;
@property (nonatomic, strong) NSTimer *toolTimerOne;
@property (nonatomic, strong) NSTimer *toolTimerTwo;
@property (nonatomic, strong) NSTimer *toolTimerThree;
@property (nonatomic, strong) LJZToolOne *toolOfOne;
@property (nonatomic, strong) LJZToolTwo *toolOfTwo;
@property (nonatomic, strong) LJZToolThree *toolOfThree;
@end
@implementation LJZGameScene
#pragma mark - Scene Cycle
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.hero flap];
}
- (void)update:(CFTimeInterval)currentTime
{
_score += 1;
if (_score > 2000){
[self changeToNextGameSceneWithScore:_score];
}
[self renderScore];
[self.hero update];
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
[self ApplyMusic];
[self setup];
}
#pragma mark - Creators
- (void)ApplyMusic
{
NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"round1_bgm" ofType:@"mp3"];
self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:NULL];
self.bgmPlayer.numberOfLoops = -1;
[self.bgmPlayer play];
}
- (void)setup
{
[self preloadSounds];
[self createWorld];
[self createScoreLabel];
[self createHero];
[self createTerrain];
[self schedulePipe];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self scheduleTool];
});
}
- (void)preloadSounds
{
self.getScoreSound = [SKAction playSoundFileNamed:@"ding.mp3" waitForCompletion:YES];
self.ggSound = [SKAction playSoundFileNamed:@"duang.mp3" waitForCompletion:YES];
}
- (void)createScoreLabel
{
self.scoreLabel = [[SKLabelNode alloc] initWithFontNamed:@"Helvetica"];
[self.scoreLabel setPosition:CGPointMake(self.size.width/2, self.size.height-100)];
[self.scoreLabel setText:[NSString stringWithFormat:@"Score %ld",self.score]];
self.scoreLabel.zPosition = 100;
[self addChild:self.scoreLabel];
}
- (void)createWorld
{
SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:@"background"];
SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture size:self.view.frame.size];
background.position = (CGPoint) {CGRectGetMidX(self.view.frame), CGRectGetMidY(self.view.frame)};
[self addChild:background];
self.scaleMode = SKSceneScaleModeAspectFit;
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0, -3);
}
- (void)createHero
{
self.hero = [LJZHero createNodeOn:self];
self.hero.position = CGPointMake(50.0f, 450.0f);
}
- (void)createTerrain
{
[LJZTerrain addNewNodeTo:self withType:1];
}
#pragma mark - Timer Manager Methods
- (void)schedulePipe
{
self.pipeTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(addPipe:) userInfo:nil repeats:YES];
[self addPipe:nil];
}
- (void)addPipe:(NSTimer *)timer
{
[LJZPipe addNewNodeTo:self withType:1];
}
- (void)scheduleTool
{
self.toolTimerOne = [NSTimer scheduledTimerWithTimeInterval:9.0 target:self selector:@selector(addToolone) userInfo:nil repeats:YES];
self.toolTimerTwo = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(addToolTwo) userInfo:nil repeats:YES];
self.toolTimerThree = [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(addToolThree) userInfo:nil repeats:YES];
}
- (void)addToolone
{
self.toolOfOne = [[LJZToolOne alloc] init];
[self.toolOfOne addNewNodeTo:self];
}
- (void)addToolTwo
{
self.toolOfTwo = [[LJZToolTwo alloc] init];
[self.toolOfTwo addNewNodeTo:self];
}
- (void)addToolThree
{
self.toolOfThree = [[LJZToolThree alloc] init];
[self.toolOfThree addNewNodeTo:self];
}
- (void)upScore100
{
self.score += 100;
}
- (void)upScore500
{
self.score += 500;
}
- (void)upScore1000
{
self.score += 1000;
}
#pragma mark - Call back methods
- (void)die
{
[self.pipeTimer invalidate];
[self.toolTimerOne invalidate];
[self.toolTimerTwo invalidate];
[self.toolTimerThree invalidate];
[self.bgmPlayer stop];
self.bgmPlayer = nil;
[self changeToResultSceneWithScore:self.score];
}
- (void)changeToResultSceneWithScore:(NSUInteger)gameScore
{
LJZResultScene *resultScene = [[LJZResultScene alloc] initWithSize:self.size score:gameScore];
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
[self.scene.view presentScene:resultScene transition:reveal];
}
- (void)changeToNextGameSceneWithScore:(NSUInteger)gameScore
{
[self.pipeTimer invalidate];
[self.toolTimerOne invalidate];
[self.toolTimerTwo invalidate];
[self.toolTimerThree invalidate];
[self.bgmPlayer stop];
self.bgmPlayer = nil;
LJZGameSceneTwo *nextScene = [[LJZGameSceneTwo alloc] initWithSize:self.size score:gameScore];
SKTransition *reveal = [SKTransition doorwayWithDuration:1.0];
[self.scene.view presentScene:nextScene transition:reveal];
}
- (void)renderScore
{
[self.scoreLabel setText:[NSString stringWithFormat:@"%ld", self.score]];
}
#pragma mark - SKPhysicsContactDelegate
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (heroType | pipeType)) {
[self.hero goDown];
[self runAction:self.ggSound completion:^{
[self die];
}];
} else if (collision == (heroType | terrainType)) {
[self runAction:self.ggSound completion:^{
[self die];
}];
} else if (collision == (heroType | toolTypeOne)) {
[self.toolOfOne FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore100];
}];
} else if (collision == (heroType | toolTypeTwo)) {
[self.toolOfTwo FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore500];
}];
} else if (collision == (heroType | toolTypeThree)) {
[self.toolOfThree FinishBitAnimate];
[self runAction:self.getScoreSound completion:^{
[self upScore1000];
}];
}
}
- (void)didEndContact:(SKPhysicsContact *)contact
{
}
@end
游戏场景不同难度的话,创建世界的方法参数不同、控制障碍与道具的定时器设置有所不同。它们都会在失败时导向结束场景。
#import
@interface LJZResultScene : SKScene
- (instancetype)initWithSize:(CGSize)size score:(NSInteger)score;
@end
#import "LJZResultScene.h"
#import "LJZMenuScene.h"
#import "LJZToast.h"
#import "LJZLoading.h"
@interface LJZResultScene()
@property (nonatomic, strong)SKLabelNode *resultLabel;
@property (nonatomic, strong)SKLabelNode *retryLabel;
@property (nonatomic, strong)SKLabelNode *uploadLabel;
@property (nonatomic, assign)NSInteger resultScore;
@end
@implementation LJZResultScene
- (instancetype)initWithSize:(CGSize)size score:(NSInteger)score
{
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
[self SetUpResultLabel:score];
[self SetUpRetryLabel];
[self SetUpUploadLabel];
}
return self;
}
#pragma mark - SetUp UI
- (void)SetUpResultLabel:(NSUInteger)score
{
self.resultScore = score;
self.resultLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
_resultLabel.text = [NSString stringWithFormat:@"score is %lu",score];
_resultLabel.fontSize = 30;
_resultLabel.fontColor = [SKColor blackColor];
_resultLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:_resultLabel];
}
- (void)SetUpRetryLabel
{
self.retryLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
_retryLabel.text = @"Try again";
_retryLabel.fontSize = 20;
_retryLabel.fontColor = [SKColor blueColor];
_retryLabel.position = CGPointMake(_resultLabel.position.x, _resultLabel.position.y * 0.8);
_retryLabel.name = @"retryLabel";
[self addChild:_retryLabel];
}
- (void)SetUpUploadLabel
{
self.uploadLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
_uploadLabel.text = @"Upload your score";
_uploadLabel.fontSize = 20;
_uploadLabel.fontColor = [SKColor blueColor];
_uploadLabel.position = CGPointMake(_resultLabel.position.x, _resultLabel.position.y * 0.6);
_uploadLabel.name = @"uploadLabel";
[self addChild:_uploadLabel];
}
#pragma mark - handleTouch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:touchLocation];
if ([node.name isEqualToString:@"retryLabel"]) {
[self changeToMenuScene];
}
else if([node.name isEqualToString:@"uploadLabel"]){
[self handleUpload];
}
}
}
- (void)changeToMenuScene
{
SKTransition *reveal = [SKTransition fadeWithDuration:.5f];
LJZMenuScene *menu = [[LJZMenuScene alloc] initWithSize: self.size];
[self.scene.view presentScene: menu transition: reveal];
}
- (void)handleUpload
{
//上传成绩处理
}
@end
本文主要介绍了游戏组件的实现以及其他场景的实现。下一节内容介绍服务端的搭建,在服务端之后将介绍客户端的网络请求以及其他细节优化。