写在前面
最近一直在忙自己的维P恩的事情
公司项目也是一团乱
于是...随手找了个游戏项目改了改就上线了,就当充数了.
SpriteKit简介
SpriteKit是iOS 7之后苹果推出的2D游戏框架。它支持2D游戏中各种功能,如物理引擎,地图编辑,粒子,视频,声音精灵化,光照等。
SpriteKit中常用的类
- SKSpriteNode 用于绘制精灵纹理
- SKVideoNode 用于播放视频
- SKLabelNode 用于渲染文本
- SKShapeNode 用于渲染基于Core Graphics路径的形状
- SKEmitterNode 用于创建和渲染粒子系统
- SKView 对象执行动画和渲染
- SKScene 游戏内容组织成的场景
- SKAction 节点动画
效果
这是一个类似于FlappyBird的小游戏
集成GameCenter
分析
结构很简单
设计思路就是障碍物不断的移动.当把角色卡死时游戏结束
代码
1.预加载游戏结束时的弹出广告
2.加载背景
3.设置physicsBody
4.设置障碍物移动Action
5.设置开始面板角色及初始Action
6.加载所有内容节点
- 初始化
- (void)initalize
{[super initalize];SKSpriteNode* background=[SKSpriteNode spriteNodeWithImageNamed:@"sky.png"];background.size = self.view.frame.size;background.position=CGPointMake(background.size.width/2, background.size.height/2);[self addChild:background];self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];self.physicsBody.categoryBitMask = edgeCategory;self.physicsWorld.contactDelegate = self;self.moveWallAction = [SKAction sequence:@[[SKAction moveToX:-WALL_WIDTH duration:TIMEINTERVAL_MOVEWALL],[SKAction removeFromParent]]];SKAction *upHeadAction = [SKAction rotateToAngle:M_PI / 6 duration:0.2f];upHeadAction.timingMode = SKActionTimingEaseOut;SKAction *downHeadAction = [SKAction rotateToAngle:-M_PI / 2 duration:0.8f];downHeadAction.timingMode = SKActionTimingEaseOut;self.moveHeadAction = [SKAction sequence:@[upHeadAction, downHeadAction,]];[self addGroundNode];[self addCeiling];[self addHeroNode];[self addResultLabelNode];[self addInstruction];[self runAction:[SKAction repeatActionForever:[SKAction sequence:@[[SKAction performSelector:@selector(addFish) onTarget:self],[SKAction waitForDuration:0.3f],]]] withKey:ACTIONKEY_ADDFISH];_interstitialObj = [[GDTMobInterstitial alloc]initWithAppkey:@"1106301022"placementId:@"2080622474511184"];_interstitialObj.delegate = self;//设置委托 _interstitialObj.isGpsOn = NO; //【可选】设置GPS开关//预加载广告[_interstitialObj loadAd];}
- 加载角色,设置飞行动作,触摸事件
- (void)addHeroNode
{self.hero=[SKSpriteNode spriteNodeWithImageNamed:@"player"];SKTexture* texture=[SKTexture textureWithImageNamed:@"player"];_hero.physicsBody=[SKPhysicsBody bodyWithTexture:texture size:_hero.size];_hero.anchorPoint = CGPointMake(0.5, 0.5);_hero.position = CGPointMake(self.frame.size.width / 2, CGRectGetMidY(self.frame));_hero.name = NODENAME_HERO;_hero.physicsBody.categoryBitMask = heroCategory;_hero.physicsBody.collisionBitMask = wallCategory | groundCategory|edgeCategory;_hero.physicsBody.contactTestBitMask = holeCategory | wallCategory | groundCategory|fishCategory;_hero.physicsBody.dynamic = YES;_hero.physicsBody.affectedByGravity = NO;_hero.physicsBody.allowsRotation = NO;_hero.physicsBody.restitution = 0.4;_hero.physicsBody.usesPreciseCollisionDetection = NO;[self addChild:_hero];
// SKTexture* texture1=[SKTexture textureWithImageNamed:@"player"];
// SKTexture* texture2=[SKTexture textureWithImageNamed:@"player3"];
//
// SKAction *animate = [SKAction animateWithTextures:@[texture1,texture2] timePerFrame:0.1];
// [_hero runAction:[SKAction repeatActionForever:animate]];[_hero runAction:[SKAction repeatActionForever:[self getFlyAction]]withKey:ACTIONKEY_FLY];
}- (SKAction *)getFlyAction
{SKAction *flyUp = [SKAction moveToY:_hero.position.y + 10 duration:0.3f];flyUp.timingMode = SKActionTimingEaseOut;SKAction *flyDown = [SKAction moveToY:_hero.position.y - 10 duration:0.3f];flyDown.timingMode = SKActionTimingEaseOut;SKAction *fly = [SKAction sequence:@[flyUp, flyDown]];return fly;
}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{if (_isGameOver) {return;}if (!_isGameStart) {[self startGame];}_hero.physicsBody.velocity = CGVectorMake(100, 500);[_hero runAction:_moveHeadAction withKey:ACTIONKEY_MOVEHEAD];
}
- 加载开始说明和结束说明
- (void)addResultLabelNode
{self.labelNode = [SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_labelNode.fontSize = 30.0f;_labelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;_labelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeTop;_labelNode.position = CGPointMake(10, self.frame.size.height - 20);_labelNode.fontColor = COLOR_LABEL;_labelNode.zPosition=100;[self addChild:_labelNode];
}
- (void)addInstruction{self.hitSakuraToScore = [SKLabelNode labelNodeWithFontNamed:@"AmericanTypewriter"];_hitSakuraToScore.fontSize = 20.0f;_hitSakuraToScore.position = CGPointMake(self.frame.size.width / 2, CGRectGetMidY(self.frame)-60);_hitSakuraToScore.fontColor = COLOR_LABEL;_hitSakuraToScore.zPosition=100;_hitSakuraToScore.text=@"Hit fish to Score";
// _hitSakuraToScore.text=NSLocalizedString(@"Hit Sakura to Score", nil);[self addChild:_hitSakuraToScore];self.tapToStart = [SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_tapToStart.fontSize = 20.0f;_tapToStart.position = CGPointMake(self.frame.size.width / 2, CGRectGetMidY(self.frame)-100);_tapToStart.fontColor = COLOR_LABEL;_tapToStart.zPosition=100;_tapToStart.text=@"Tap to Jump";[self addChild:_tapToStart];
}
- 加载障碍物
- (void)addWall
{CGFloat spaceHeigh = self.frame.size.height - GROUND_HEIGHT;float random= arc4random() % 4;CGFloat holeLength = HERO_SIZE.height * (2.0+random*0.1);int holePosition = arc4random() % (int)((spaceHeigh - holeLength) / HERO_SIZE.height);CGFloat x = self.frame.size.width;CGFloat upHeight = holePosition * HERO_SIZE.height;if (upHeight > 0) {SKSpriteNode *upWall = [SKSpriteNode spriteNodeWithColor:COLOR_WALL size:CGSizeMake(WALL_WIDTH, upHeight)];upWall.anchorPoint = CGPointMake(0, 0);upWall.position = CGPointMake(x, self.frame.size.height - upHeight);upWall.name = NODENAME_WALL;upWall.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:upWall.size center:CGPointMake(upWall.size.width / 2.0f, upWall.size.height / 2.0f)];upWall.physicsBody.categoryBitMask = wallCategory;upWall.physicsBody.dynamic = NO;upWall.physicsBody.friction = 0;[upWall runAction:_moveWallAction withKey:ACTIONKEY_MOVEWALL];[self addChild:upWall];}CGFloat downHeight = spaceHeigh - upHeight - holeLength;if (downHeight > 0) {SKSpriteNode *downWall = [SKSpriteNode spriteNodeWithColor:COLOR_WALL size:CGSizeMake(WALL_WIDTH, downHeight)];downWall.anchorPoint = CGPointMake(0, 0);downWall.position = CGPointMake(x, GROUND_HEIGHT);downWall.name = NODENAME_WALL;downWall.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:downWall.size center:CGPointMake(downWall.size.width / 2.0f, downWall.size.height / 2.0f)];downWall.physicsBody.categoryBitMask = wallCategory;downWall.physicsBody.dynamic = NO;downWall.physicsBody.friction = 0;[downWall runAction:_moveWallAction withKey:ACTIONKEY_MOVEWALL];[self addChild:downWall];}SKSpriteNode *hole = [SKSpriteNode spriteNodeWithColor:[UIColor clearColor] size:CGSizeMake(WALL_WIDTH, holeLength)];hole.anchorPoint = CGPointMake(0, 0);hole.position = CGPointMake(x, self.frame.size.height - upHeight - holeLength);hole.name = NODENAME_HOLE;hole.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:hole.size center:CGPointMake(hole.size.width / 2.0f, hole.size.height / 2.0f)];hole.physicsBody.categoryBitMask = holeCategory;hole.physicsBody.dynamic = NO;[hole runAction:_moveWallAction withKey:ACTIONKEY_MOVEWALL];[self addChild:hole];
}
- 游戏开始时 不断增加障碍物
- (void)startGame
{self.isGameStart = YES;_hero.physicsBody.affectedByGravity = YES;[_hero removeActionForKey:ACTIONKEY_FLY];[_tapToStart removeFromParent];[_hitSakuraToScore removeFromParent];[self addResultLabelNode];SKAction *addWall = [SKAction sequence:@[[SKAction performSelector:@selector(addWall) onTarget:self],[SKAction waitForDuration:TIMEINTERVAL_ADDWALL],]];[self runAction:[SKAction repeatActionForever:addWall] withKey:ACTIONKEY_ADDWALL];
}
- 实时更新内容
- (void)update:(NSTimeInterval)currentTime
{if(self.hero&&!_isGameOver){if ( self.hero.position.x<10) {[self gameOver];}else if(self.hero.position.x>self.frame.size.width){self.hero.position =CGPointMake(self.hero.position.x-20, self.hero.position.y);}}__block int wallCount = 0;[self enumerateChildNodesWithName:NODENAME_WALL usingBlock:^(SKNode *node, BOOL *stop) {if (wallCount >= 2) {*stop = YES;return;}if (node.position.x <= -WALL_WIDTH) {wallCount++;[node removeFromParent];}}];[self enumerateChildNodesWithName:NODENAME_HOLE usingBlock:^(SKNode *node, BOOL *stop) {if (node.position.x <= -WALL_WIDTH) {[node removeFromParent];*stop = YES;}}];[self enumerateChildNodesWithName:NODENAME_FISH usingBlock:^(SKNode *node, BOOL *stop) {if (node.position.x <= -node.frame.size.width) {[node removeFromParent];}}];
}
- 设置物体碰撞效果
- (void)didBeginContact:(SKPhysicsContact *)contact
{if (_isGameOver) {return;}SKPhysicsBody *firstBody, *secondBody;if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {firstBody = contact.bodyA;secondBody = contact.bodyB;} else {firstBody = contact.bodyB;secondBody = contact.bodyA;}if ((firstBody.categoryBitMask & heroCategory) && (secondBody.categoryBitMask & fishCategory)) {if(secondBody.node.parent&&self.isGameStart){int currentPoint = [_labelNode.text intValue];_labelNode.text = [NSString stringWithFormat:@"%d", currentPoint + 1];[self playSoundWithName:@"sfx_wing.caf"];NSString *burstPath =[[NSBundle mainBundle]pathForResource:@"MyParticle" ofType:@"sks"];SKEmitterNode *burstNode =[NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];burstNode.position = secondBody.node.position;[secondBody.node removeFromParent];[self addChild:burstNode];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[burstNode runAction:[SKAction removeFromParent]];});}}
}
- (void) didEndContact:(SKPhysicsContact *)contact{if (_isGameOver) {return;}SKPhysicsBody *firstBody, *secondBody;if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {firstBody = contact.bodyA;secondBody = contact.bodyB;} else {firstBody = contact.bodyB;secondBody = contact.bodyA;}return;}- (void)playSoundWithName:(NSString *)fileName
{dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[self runAction:[SKAction playSoundFileNamed:fileName waitForCompletion:YES]];});
}
- 游戏结束与重新开始
- (void)gameOver
{self.isGameOver = YES;self.isGameStart=NO;[_hero removeActionForKey:ACTIONKEY_MOVEHEAD];[self removeActionForKey:ACTIONKEY_ADDWALL];[self enumerateChildNodesWithName:NODENAME_WALL usingBlock:^(SKNode *node, BOOL *stop) {[node removeActionForKey:ACTIONKEY_MOVEWALL];}];[self enumerateChildNodesWithName:NODENAME_HOLE usingBlock:^(SKNode *node, BOOL *stop) {[node removeActionForKey:ACTIONKEY_MOVEWALL];}];if([_labelNode.text isEqualToString:@""])_labelNode.text=@"0";NSString *result=_labelNode.text;RestartLabel *restartView = [RestartLabel getInstanceWithSize:self.size Point:result];restartView.delegate = self;[restartView showInScene:self];_labelNode.text=@"";if (_interstitialObj.isReady) {UIViewController *vc = [[[UIApplication sharedApplication] keyWindow] rootViewController];//vc = [self navigationController];[_interstitialObj presentFromRootViewController:vc];}}- (void)restart
{[self addInstruction];self.labelNode.text = @"";[self enumerateChildNodesWithName:NODENAME_HOLE usingBlock:^(SKNode *node, BOOL *stop) {[node removeFromParent];}];[self enumerateChildNodesWithName:NODENAME_WALL usingBlock:^(SKNode *node, BOOL *stop) {[node removeFromParent];}];[_hero removeFromParent];self.hero = nil;[self addHeroNode];[self runAction:[SKAction repeatActionForever:[SKAction sequence:@[[SKAction performSelector:@selector(addFish) onTarget:self],[SKAction waitForDuration:0.3f],]]] withKey:ACTIONKEY_ADDFISH];self.isGameStart = NO;self.isGameOver = NO;
}- (void)restartView:(RestartLabel *)restartView didPressRestartButton:(SKSpriteNode *)restartButton
{[restartView dismiss];[self restart];}
- (void)restartView:(RestartLabel *)restartView didPressLeaderboardButton:(SKSpriteNode *)restartButton{[self showLeaderboard];
}
- 游戏结束可以调期GameCenter排行榜
-(void)showLeaderboard{GKGameCenterViewController *gcViewController = [[GKGameCenterViewController alloc] init];gcViewController.gameCenterDelegate = self;gcViewController.viewState = GKGameCenterViewControllerStateLeaderboards;gcViewController.leaderboardIdentifier = @"MyFirstLeaderboard";[self.view.window.rootViewController presentViewController:gcViewController animated:YES completion:nil];}
-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController
{[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
- 积分框
@interface ScoreLabel : SKSpriteNode@property(nonatomic, copy) NSString* finalPoint;@end#import "ScoreLabel.h"@implementation ScoreLabel- (id)initWithColor:(UIColor *)color size:(CGSize)size
{if (self = [super initWithColor:color size:size]) {SKLabelNode* scoreLabelNode = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];scoreLabelNode.text=_finalPoint;scoreLabelNode.fontSize = 20.0f;scoreLabelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;scoreLabelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;scoreLabelNode.position = CGPointMake(size.width / 2.0f, size.height - 300);scoreLabelNode.fontColor = [UIColor whiteColor];[self addChild:scoreLabelNode]; }return self;
}@end
- 游戏结束节点内容
@class RestartLabel;
@protocol RestartViewDelegate <NSObject>- (void)restartView:(RestartLabel *)restartView didPressRestartButton:(SKSpriteNode *)restartButton;
- (void)restartView:(RestartLabel *)restartView didPressLeaderboardButton:(SKSpriteNode *)restartButton;
@end@interface RestartLabel : SKSpriteNode@property (weak, nonatomic) id <RestartViewDelegate> delegate;
@property (copy, nonatomic) NSString* finalPoint;
+ (RestartLabel *)getInstanceWithSize:(CGSize)size Point:(NSString *)point;
- (void)dismiss;
- (void)showInScene:(SKScene *)scene;@end#define NODENAME_BUTTON @"button"
#import "RestartLabel.h"
#import "MainViewController.h"@import GameKit;
@interface RestartLabel()
@property (strong, nonatomic) SKSpriteNode *button;
@property (strong, nonatomic) SKLabelNode *labelNode;
@property (strong, nonatomic) SKLabelNode *scoreLabelNode;
@property (strong, nonatomic) SKLabelNode *highestLabelNode;
@property (strong, nonatomic) SKSpriteNode *gameCenterNode;
@property (strong, nonatomic) SKLabelNode *gameCenterLabel;
@end@implementation RestartLabel- (id)initWithColor:(UIColor *)color size:(CGSize)size
{if (self = [super initWithColor:color size:size]) {self.userInteractionEnabled = YES;self.button = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0.608 green:0.349 blue:0.714 alpha:1] size:CGSizeMake(100, 50)];_button.position = CGPointMake(size.width / 2.0f, size.height - 350);_button.name = NODENAME_BUTTON;[self addChild:_button];self.labelNode = [SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_labelNode.text = @"Restart";_labelNode.fontSize = 20.0f;_labelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;_labelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;_labelNode.position = CGPointMake(0, 0);_labelNode.fontColor = [UIColor whiteColor];[_button addChild:_labelNode];self.gameCenterNode = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0.608 green:0.349 blue:0.714 alpha:1]size:CGSizeMake(150, 50)];_gameCenterNode.position = CGPointMake(size.width / 2.0f, size.height - 280);[self addChild:_gameCenterNode];self. gameCenterLabel=[SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_gameCenterLabel.text = @"Leaderboard";_gameCenterLabel.fontSize = 20.0f;_gameCenterLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;_gameCenterLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;_gameCenterLabel.position = CGPointMake(0, 0);_gameCenterLabel.fontColor = [UIColor whiteColor];[_gameCenterNode addChild:_gameCenterLabel];}return self;
}
-(void)addScoreLabelSize:(CGSize)size{_scoreLabelNode = [SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_scoreLabelNode.text=[NSString stringWithFormat:@"Your Score: \r%@",_finalPoint? _finalPoint: @"0"];_scoreLabelNode.fontSize = 20.0f;_scoreLabelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;_scoreLabelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;_scoreLabelNode.position = CGPointMake(size.width / 2.0f, size.height - 170);_scoreLabelNode.fontColor = [UIColor colorWithRed:0.173 green:0.243 blue:0.314 alpha:1];[self addChild:_scoreLabelNode];
}-(void)addHighestLabelSize:(CGSize)size{_highestLabelNode = [SKLabelNode labelNodeWithFontNamed:@"PingFangSC-Regular"];_highestLabelNode.fontColor = [UIColor colorWithRed:0.173 green:0.243 blue:0.314 alpha:1];NSString* showText;NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];NSNumber* highestScore=[defaults objectForKey:@"HighScore"];NSNumber* currentPoint= [NSNumber numberWithInt: [_finalPoint intValue]];if(highestScore==nil||[currentPoint integerValue]>[highestScore integerValue]){[defaults setObject:currentPoint forKey:@"HighScore"];highestScore=currentPoint;showText=@"New Record!";_highestLabelNode.fontColor=[UIColor colorWithRed:0.753 green:0.224 blue:0.169 alpha:1];[defaults synchronize];}else{showText=[NSString stringWithFormat:@"High Score: \r%lu",(long)[highestScore integerValue]];}if(highestScore!=nil){[self reportScore:[highestScore integerValue]];}_highestLabelNode.text=showText;_highestLabelNode.fontSize = 20.0f;_highestLabelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;_highestLabelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;_highestLabelNode.position = CGPointMake(size.width / 2.0f, size.height - 220);[self addChild:_highestLabelNode];
}+ (RestartLabel *)getInstanceWithSize:(CGSize)size Point:(NSString *)point
{RestartLabel *restartView = [RestartLabel spriteNodeWithColor:color(255, 255, 255, 0.6) size:size];restartView.anchorPoint = CGPointMake(0, 0);restartView.finalPoint=point;[restartView addScoreLabelSize:size];[restartView addHighestLabelSize:size];return restartView;
}- (void)showInScene:(SKScene *)scene
{self.alpha = 0.0f;[scene addChild:self];[self runAction:[SKAction fadeInWithDuration:0.3f]];
}- (void)dismiss
{[self runAction:[SKAction fadeOutWithDuration:0.3f] completion:^{[self removeFromParent];}];
}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{UITouch *touch = [touches anyObject];CGPoint location = [touch locationInNode:self];SKNode *touchNode = [self nodeAtPoint:location];if (touchNode == _button || touchNode == _labelNode) {if ([_delegate respondsToSelector:@selector(restartView:didPressRestartButton:)]) {[_delegate restartView:self didPressRestartButton:_button];}}else if(touchNode==_gameCenterNode || touchNode==_gameCenterLabel){if ([_delegate respondsToSelector:@selector(restartView:didPressLeaderboardButton:)]) {[_delegate restartView:self didPressLeaderboardButton:_button];}
}
}
-(void)reportScore:(NSInteger)inputScore{GKScore *score = [[GKScore alloc] initWithLeaderboardIdentifier:@"MyFirstLeaderboard"];score.value = inputScore;[GKScore reportScores:@[score] withCompletionHandler:^(NSError *error) {if (error != nil) {NSLog(@"%@", [error localizedDescription]);}}];
}@end
关于游戏上架Tips
蛋疼广电粽菊要求国内游戏必须备案...
我们只是想上个小游戏而已~难道还要再等个大半个月去备案么?
Apple也妥协了 在备注那里要求中国区上架游戏必须填写备案号
But!!!上有政策,下有对策嘛~
- 填写App分类时直接选择
娱乐
类型上架,就不会要求填写备案号了~ - 销售范围,不选择中国地区,这样也不会要求填写备案号,等过审了,再将销售范围改回所有地区,基本上是实时生效~
以上两种方式屡试不爽哈~对于我们个人小开发来说也算是个小福利了.
Demo地址
Github地址,欢迎Star (由于集成了广告,广点通的静态库需要单独下载下完直接扔到项目里就行)
已上架Appstore 猫爷快吃 喜欢就支持下吧~
欢迎光顾自己的小站,内容都是同步更新的~
大家低调支持下自己的 牛牛数据 Half-price~~
还没结束
快来猜猜我放的背景音乐是啥~