【完整APP】SpriteKit引擎开发iOS小游戏之五(移动端网络与优化)【完结】

【网络动画效果】

众所周知,包括但不限于网络处理,很多使用APP的时机都需要展示Loading或者Toast提示的形式来提升用户的交互体验。

  1. 自定义Loading类:是继承UIActivityIndicatorView的子类。简化创建与管理。指定了布局与样式等。对外暴露创建与消失方法。
#import "LJZLoading.h"

@implementation LJZLoading

- (instancetype)initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style
{
    self = [super initWithActivityIndicatorStyle:style];
    if(self){
        self.hidesWhenStopped = YES;
        self.frame = CGRectMake(0, 0, 150, 150);
        self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2);
        self.backgroundColor = [UIColor blackColor];
        self.alpha = 0.5;
        [[[UIApplication sharedApplication] keyWindow] addSubview:self];
        [self startAnimating];
    }
    return self;
}

- (void)StopShowLoading
{
    [self stopAnimating];
    [self removeFromSuperview];
}

@end
  1. 自定义Toast类”LJZToast“:设计为以单例模式调用,控制内容与展示时长,在固定位置展示出提示信息。其头文件如下:
#import 
#import 
@interface ToastLabel : UILabel
- (void)setMessageText:(NSString *)text;
@end

@interface LJZToast : NSObject
@property (nonatomic,strong)ToastLabel *toastLabel;

+ (instancetype)shareInstance;
- (void)ShowToast:(NSString *)message duration:(CGFloat)duration;

@end
  1. Toast的实现上,UILabel子类的样式自定义,dispatch_once执行静态单例。dispatch_after控制展示animateWithDuration实现消失动画,具体实现如下:
#import "LJZToast.h"
@implementation ToastLabel

- (instancetype)init
{
    self = [super init];
    if(self){
        self.layer.cornerRadius = 8;
        self.layer.masksToBounds = YES;
        self.backgroundColor = [UIColor blackColor];
        self.numberOfLines = 0;
        self.textAlignment = NSTextAlignmentCenter;
        self.textColor = [UIColor whiteColor];
        self.font = [UIFont systemFontOfSize:15];
    }
    return self;
}

- (void)setMessageText:(NSString *)text
{
    [self setText:text];
    self.frame = CGRectMake(0, 0, 150, 50);
    self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, 150);
}

@end


@implementation LJZToast

+ (instancetype)shareInstance
{
    static LJZToast *singleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleton = [[LJZToast alloc] init];
    });
    return singleton;
}

- (instancetype)init
{
    self = [super init];
    if(self){
        self.toastLabel = [[ToastLabel alloc] init];
    }
    return self;
}

- (void)ShowToast:(NSString *)message duration:(CGFloat)duration
{
    if([message length] == 0){
        return;
    }
    [_toastLabel setMessageText:message];
    _toastLabel.alpha = 0.8;
    [[[UIApplication sharedApplication] keyWindow] addSubview:_toastLabel];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self changeTime];
    });
}
- (void)changeTime
{
    [UIView animateWithDuration:0.2f animations:^{
        _toastLabel.alpha = 0;
    }completion:^(BOOL finished) {
        [_toastLabel removeFromSuperview];
    }];
}
@end

【移动端网络请求】

  • NSURLSession
    对于iOS而言,网络编程主要依赖两种方式:NSURLSession以及NSURLConnection。在iOS9.0之后,NSURLConnection过期不再使用。本系统使用NSURLSession来完成客户端的网络模块。NSURLSession具有以下优势
    (1)NSURLSession支持http2.0协议。
    (2) NSURLSession在下载任务时会直接写入沙盒和tem文件夹中,不会放入内存避免内存暴涨。
    (3)NSURLSession提供来全局的session并且可以统一配置,每一个实例也可单独配置。
    (4)NSURLSession下载支持断点续传,可以取消暂停继续,使用多线程异步处理下载。
  • 类NSURLSessionTask
    NSURLSessionTask是一个抽象类,如图4-3展示,使用时根据具体需求使用子类。
    【完整APP】SpriteKit引擎开发iOS小游戏之五(移动端网络与优化)【完结】_第1张图片
  • 请求过程与解析
    在游戏中,用户需要在登陆,注册,上传成绩,以及查看排行等地方进行网络请求,NSURLSession的使用非常简便,根据会话对象创建一个请求Task然后执行,请求过程
    【完整APP】SpriteKit引擎开发iOS小游戏之五(移动端网络与优化)【完结】_第2张图片

结合Loading与Toast的使用,APP中主要的网络请求实现如下:

  1. 登录请求与处理部分
- (void)HandleLogin
{
    [self.view endEditing:YES];
    NSString *username = self.account.text;
    NSString *password = self.password.text;
    //输入合法性检验
    if (username.length == 0 || password.length == 0){
        [[LJZToast shareInstance] ShowToast:@"用户名与密码不能为空!" duration:1];
        return;
    }
    
    LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/login?username=%@&password=%@",username,password]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(error == nil) {
            NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
            NSLog(@"reponse data:%@",responseInfo);
            if([responseInfo isEqualToString:@"enable"]){
                NSUserDefaults *userDefault= [NSUserDefaults standardUserDefaults];
                [userDefault setObject:username forKey:@"user_name"];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"登录成功!" duration:1];
                    [self dismissViewControllerAnimated:YES completion:nil];
                });
            } else{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"登录失败,请检查用户名与密码!" duration:1];
                });
            }
        }else{
            dispatch_async(dispatch_get_main_queue(), ^{
                [loadingView StopShowLoading];
                [[LJZToast shareInstance] ShowToast:@"请检查网络!" duration:1];
            });
            NSLog(@"server error:%@",error);
        }
    }];
    [dataTask resume];
}
  1. 注册处理
- (void)HandleRegister
{
    NSString *username = self.account.text;
    NSString *password = self.password.text;
    if (username.length == 0 || password.length == 0){
        [[LJZToast shareInstance] ShowToast:@"用户名与密码不能为空!" duration:1];
        return;
    }else if (username.length > 15 || password.length >15){
        [[LJZToast shareInstance] ShowToast:@"用户名与密码不超过15位!" duration:1];
        return;
    }
    //输入合法性检验
    LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/register?username=%@&password=%@",username,password]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if(error == nil) {
                NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
                NSLog(@"reponse data:%@",responseInfo);
                if([responseInfo isEqualToString:@"enable"]){
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"注册成功!" duration:1];
                        [self dismissViewControllerAnimated:YES completion:nil];
                    });
                } else{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"该名字已被注册!" duration:1];
                    });
                }
            }else{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"请检查网络!" duration:1];
                });
                
                NSLog(@"server error:%@",error);
            }
    }];
    [dataTask resume];
}
  1. 请求排行榜数据
- (void)GetPlayersInfo
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    NSString *username = [userDefault objectForKey:@"user_name"];
    NSString *isLogin;
    if (username == nil){
        _playerAccoutRank.text = @"-";
        _playerAccoutScore.text = @"-";
        _playerAccoutName.text = @"未登陆";
        isLogin = @"no";
    } else{
        _playerAccoutName.text = [NSString stringWithFormat:@"%@",username];
        isLogin = @"yes";
    }

    //请求排行榜数据
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/rankInfo?username=%@&islogin=%@",username,isLogin]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if(error == nil) {
                NSDictionary *responseInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
                NSLog(@"reponse data:%@",responseInfo);
                if ([isLogin isEqualToString:@"yes"]){
                    _playerRank = responseInfo[@"self_rank"];
                    _playerScore = responseInfo[@"self_score"];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self ShowSelfInfo];
                    });
                }
                _firstPlayerName = responseInfo[@"p1_name"];
                _secondPlayerName = responseInfo[@"p2_name"];
                _thirdPlayerName = responseInfo[@"p3_name"];
                _fourthPlayerName = responseInfo[@"p4_name"];
                _fifthPlayerName = responseInfo[@"p5_name"];
                _sixthPlayerName = responseInfo[@"p6_name"];
                _firstPlayerScore = responseInfo[@"p1_score"];
                _secondPlayerScore = responseInfo[@"p2_score"];
                _thirdPlayerScore = responseInfo[@"p3_score"];
                _fourthPlayerScore = responseInfo[@"p4_score"];
                _fifthPlayerScore = responseInfo[@"p5_score"];
                _sixthPlayerScore = responseInfo[@"p6_score"];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self ShowRankInfo];
                });
            }else{
                [[LJZToast shareInstance] ShowToast:@"请检查网络!" duration:1];
                NSLog(@"server error:%@",error);
            }
    }];
    [dataTask resume];
}
  1. 在游戏结束界面上传新成绩
- (void)handleUpload
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    NSString *username = [userDefault objectForKey:@"user_name"];
    if (username == nil){
        [[LJZToast shareInstance] ShowToast:@"未登陆不可上传成绩" duration:1];
    } else{
        LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/upLoadScore?username=%@&score=%ld",username,self.resultScore]];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                if(error == nil) {
                    NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
                    NSLog(@"reponse data:%@",responseInfo);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:[NSString stringWithFormat:@"%@",responseInfo] duration:1];
                    });
                }else{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"上传成绩失败,请检查网络!" duration:1];
                    });
                    NSLog(@"server error:%@",error);
                }
        }];
        [dataTask resume];
    }
}

【其他优化】

  1. 合理的地方加入主线程保护:主要为UI改动。
dispatch_async(dispatch_get_main_queue(), ^{
                    //要做的事情
                    });
  1. 注意释放内存
//移除通知
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//场景中。。。
- (void)die
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    [self changeToResultSceneWithScore:self.score];
}
  1. 耗时与持续工作放入后台线程运行。
  2. 在关键的工作节点与事件触发中打出Log信息,按一定规则打印,便于测试。

【未来展望】

作为游戏APP算是搭建比较完善了。游戏组件的抽离也有利于后续的扩展游戏功能。存在包括不限以下的ToDo:

  1. 游戏角色单一,是否可添加游戏角色的选择。
  2. 游戏账号货币的积累,开放游戏商城功能。
  3. 游戏内容或者截图分享?
  4. 道具与障碍的有待多元化?
  5. 支持多人运动飞行?

你可能感兴趣的:(小项目)