interface ViewController ()
Now remove the extra methods: loadShaders, compileShader:type:file:, linkProgram:, validateProgram:
in our simplest example we will not use shaders. Of course, you can use them if you know how and why, but I did not bother with it =).setupGL
and tearDownGL
we bring to the form: - (void)setupGL { [EAGLContextsetCurrentContext:self.context]; self.effect = [[GLKBaseEffectalloc] init]; } - (void)tearDownGL { [EAGLContextsetCurrentContext:self.context]; self.effect = nil; }
#define kGameStateNone 0 #define kGameStateLose 1 #define kGameStateWon 2 @property (assign) int gameState; // see kGameState... @property (assign) BOOL gameRunning; - (void)loadBricks; - (void)startGame; - (void)endGameWithWin:(BOOL)win;
@interface GameSprite : NSObject - (id)initWithTexture:(GLKTextureInfo *)textureInfo effect:(GLKBaseEffect *)effect; - (id)initWithImage:(UIImage *)image effect:(GLKBaseEffect *)effect; - (void)render; - (void)update:(float)dt; - (CGRect)boundingRect; @property (assign) GLKVector2 position; @property (assign) CGSize contentSize; @property (assign) GLKVector2 moveVelocity; // points/sec @end
typedef struct { CGPoint geometryVertex; CGPoint textureVertex; } TexturedVertex; typedef struct { TexturedVertex bl; TexturedVertex br; TexturedVertex tl; TexturedVertex tr; } TexturedQuad;
@interface GameSprite() @property (strong) GLKBaseEffect *effect; @property (assign) TexturedQuad quad; @property (strong) GLKTextureInfo *textureInfo; - (void)initQuadAndSize; @end
- (id)initWithTexture:(GLKTextureInfo *)textureInfo effect:(GLKBaseEffect *)effect { if ((self = [super init])) { self.effect = effect; self.textureInfo = textureInfo; if (self.textureInfo == nil) { NSLog(@"Error loading texture! Texture info is nil!"); return nil; } [self initQuadAndSize]; } return self; } - (id)initWithImage:(UIImage *)image effect:(GLKBaseEffect *)effect { if ((self = [super init])) { self.effect = effect; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil]; NSError *error; self.textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:options error:&error]; if (self.textureInfo == nil) { NSLog(@"Error loading image: %@", [error localizedDescription]); return nil; } [self initQuadAndSize]; } return self; } - (void)initQuadAndSize { self.contentSize = CGSizeMake(self.textureInfo.width, self.textureInfo.height); TexturedQuad newQuad; newQuad.bl.geometryVertex = CGPointMake(0, 0); newQuad.br.geometryVertex = CGPointMake(self.textureInfo.width, 0); newQuad.tl.geometryVertex = CGPointMake(0, self.textureInfo.height); newQuad.tr.geometryVertex = CGPointMake(self.textureInfo.width, self.textureInfo.height); newQuad.bl.textureVertex = CGPointMake(0, 0); newQuad.br.textureVertex = CGPointMake(1, 0); newQuad.tl.textureVertex = CGPointMake(0, 1); newQuad.tr.textureVertex = CGPointMake(1, 1); self.quad = newQuad; }
render
method! - (void)render { self.effect.texture2d0.name = self.textureInfo.name; self.effect.texture2d0.enabled = YES; self.effect.transform.modelviewMatrix = self.modelMatrix; [self.effect prepareToDraw]; long offset = (long)&_quad; glEnableVertexAttribArray(GLKVertexAttribPosition); glEnableVertexAttribArray(GLKVertexAttribTexCoord0); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void *) (offset + offsetof(TexturedVertex, geometryVertex))); glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void *) (offset + offsetof(TexturedVertex, textureVertex))); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
- (GLKMatrix4)modelMatrix { GLKMatrix4 modelMatrix = GLKMatrix4Identity; modelMatrix = GLKMatrix4Translate(modelMatrix, self.position.x, self.position.y, 0); modelMatrix = GLKMatrix4Translate(modelMatrix, -self.contentSize.width / 2, -self.contentSize.height / 2, 0); return modelMatrix; }
update:
we can create our first sprite and load a picture from resources into it. And in our ViewController to draw it: - (void)viewDidLoad { [super viewDidLoad]; self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; if (!self.context) { NSLog(@"Failed to create ES context"); } [self setupGL]; GLKView *view = (GLKView *)self.view; view.context = self.context; GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, 320, 0, 480, -1024, 1024); self.effect.transform.projectionMatrix = projectionMatrix; // initializing game state self.gameRunning = NO; self.gameState = kGameStateNone; // initializing sprites self.testSprite = [[GameSpritealloc] initWithImage:[UIImageimageNamed:@"myImage"] effect:self.effect]; self.testSprite .position = GLKVector2Make(160, 35); } - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { glClearColor(1.f, 1.f, 1.f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); [self.testSprite render]; }
@property (strong, nonatomic) GameSprite *playerBat; @property (strong, nonatomic) GameSprite *ball; @property (strong, nonatomic) GameSprite *background; @property (strong, nonatomic) GameSprite *menuDimmer; @property (strong, nonatomic) GameSprite *menuCaption; @property (strong, nonatomic) GameSprite *menuCaptionWon; @property (strong, nonatomic) GameSprite *menuCaptionLose; @property (strong, nonatomic) GameSprite *menuStartButton; @property (strong, nonatomic) NSMutableArray *bricks;
101101 111111 010010 111111 000000 111111
- (void)loadBricks { // assuming 6x6 brick matrix, each brick is 50x10 NSError *error; [NSBundle mainBundle] ; NSStringEncoding encoding; NSString *filePath = [[NSBundle mainBundle] pathForResource:@"level1" ofType:@"txt"]; NSString *levelData = [NSString stringWithContentsOfFile:filePath usedEncoding:&encoding error:&error]; if (levelData == nil) { NSLog(@"Error loading level data! %@", error); return; } levelData = [[levelData componentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString: @""]; if ([levelData length] < (6*6)) { NSLog(@"Level data has incorrect size!"); return; } NSMutableArray *loadedBricks = [NSMutableArray array]; UIImage *brickImage = [UIImage imageNamed:@"brick1"]; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil]; GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:brickImage.CGImage options:options error:&error]; if (textureInfo == nil) { NSLog(@"Error loading image: %@", [error localizedDescription]); return; } for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { if ([levelData characterAtIndex:j + i * 6] == '1') { GameSprite *brickSprite = [[GameSprite alloc] initWithTexture:textureInfo effect:self.effect]; brickSprite.position = GLKVector2Make((j + 1) * 50.f - 15.f, 480.f - (i + 1) * 10.f - 15.f); [loadedBricks addObject:brickSprite]; } } } self.bricks = loadedBricks; }
glkView:drawInRect:
Add a drawing in order: first the background, then the beat, then all the bricks, and at the end the ball. And now you can and admire! =)update:
method update:
you need to take this speed into account and change the coordinates: - (void)update:(float)dt { GLKVector2 curMove = GLKVector2MultiplyScalar(self.moveVelocity, dt); self.position = GLKVector2Add(self.position, curMove); }
update
method of our ViewController, you need to update the ball sprite: [self.ball update:self.timeSinceLastUpdate];
startGame
- and the ball will fly! - (void)startGame { self.gameRunning = YES; self.gameState = kGameStateNone; [selfloadBricks]; self.ball.position = GLKVector2Make(160, 80); self.ball.moveVelocity = GLKVector2Make(120, 240); }
viewDidLoad
- and the ball will fly, but - will quickly fly off the screen. Hmm, sad! Well, we start thinking about handling collisions and collisions of a ball with walls. In the ready-made update
method, add the first ball collision with walls: // checking for walls // left if (self.ball.boundingRect.origin.x <= 0) { self.ball.moveVelocity = GLKVector2Make(-self.ball.moveVelocity.x, self.ball.moveVelocity.y); self.ball.position = GLKVector2Make(self.ball.position.x - self.ball.boundingRect.origin.x, self.ball.position.y); } // right if (self.ball.boundingRect.origin.x + self.ball.boundingRect.size.width >= 320) { self.ball.moveVelocity = GLKVector2Make(-self.ball.moveVelocity.x, self.ball.moveVelocity.y); self.ball.position = GLKVector2Make(self.ball.position.x - (self.ball.boundingRect.size.width + self.ball.boundingRect.origin.x - 320), self.ball.position.y); } // top if (self.ball.boundingRect.origin.y + self.ball.boundingRect.size.height >= 480) { self.ball.moveVelocity = GLKVector2Make(self.ball.moveVelocity.x, -self.ball.moveVelocity.y); self.ball.position = GLKVector2Make(self.ball.position.x, self.ball.position.y - (self.ball.boundingRect.origin.y + self.ball.boundingRect.size.height - 480)); } // bottom (player lose) if (self.ball.boundingRect.origin.y + self.ball.boundingRect.size.height <= 70) { [self endGameWithWin:NO]; }
// player strikes! if (CGRectIntersectsRect(self.ball.boundingRect, self.playerBat.boundingRect)) { float angleCoef = (self.ball.position.x - self.playerBat.position.x) / (self.playerBat.contentSize.width / 2); float newAngle = 90.f - angleCoef * 80.f; GLKVector2 ballDirection = GLKVector2Normalize(GLKVector2Make(1 / tanf(GLKMathDegreesToRadians(newAngle)), 1)); float ballSpeed = GLKVector2Length(self.ball.moveVelocity); self.ball.moveVelocity = GLKVector2MultiplyScalar(ballDirection, ballSpeed); self.ball.position = GLKVector2Make(self.ball.position.x, self.ball.position.y + (self.playerBat.boundingRect.origin.y + self.playerBat.boundingRect.size.height - self.ball.boundingRect.origin.y)); }
// checking for broken bricks NSMutableArray *brokenBricks = [NSMutableArray array]; GLKVector2 initialBallVelocity = self.ball.moveVelocity; for (GameSprite *brick in self.bricks) { if (CGRectIntersectsRect(self.ball.boundingRect, brick.boundingRect)) { [brokenBricks addObject: brick]; if ((self.ball.position.y < brick.position.y - brick.contentSize.height / 2) || (self.ball.position.y > brick.position.y + brick.contentSize.height / 2)) { self.ball.moveVelocity = GLKVector2Make(initialBallVelocity.x, -initialBallVelocity.y); } else { self.ball.moveVelocity = GLKVector2Make(-initialBallVelocity.x, initialBallVelocity.y); } } } // removing them for (GameSprite *brick in brokenBricks) { [self.bricks removeObject:brick]; } if (self.bricks.count == 0) { [self endGameWithWin:YES]; }
- (void)viewDidLoad { // ... // gestures UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGestureFrom:)]; [self.view addGestureRecognizer:panRecognizer]; [self.view addGestureRecognizer:tapRecognizer]; } - (void)handleTapGestureFrom:(UITapGestureRecognizer *)recognizer { CGPoint touchLocation = [recognizer locationInView:recognizer.view]; if (self.gameRunning) { GLKVector2 target = GLKVector2Make(touchLocation.x, self.playerBat.position.y); self.playerBat.position = target; } } - (void)handlePanGesture:(UIGestureRecognizer *)gestureRecognizer { CGPoint touchLocation = [gestureRecognizer locationInView:gestureRecognizer.view]; if (self.gameRunning) { GLKVector2 target = GLKVector2Make(touchLocation.x, self.playerBat.position.y); self.playerBat.position = target; } }
glkView:drawInRect:
we need to check the state of the game and draw everything as it should: - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { glClearColor(1.f, 1.f, 1.f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); [self.background render]; [self.playerBat render]; for (GameSprite *brick in self.bricks) { [brick render]; } [self.ball render]; if (!self.gameRunning) { [self.menuDimmer render]; [self.menuStartButton render]; switch (self.gameState) { case kGameStateWon: [self.menuCaptionWon render]; break; case kGameStateLose: [self.menuCaptionLose render]; break; case kGameStateNone: default: [self.menuCaption render]; break; } } }
handleTapGestureFrom:
the "else" block: else if (CGRectContainsPoint(self.menuStartButton.boundingRect, touchLocation)) { [self startGame]; }
Source: https://habr.com/ru/post/151762/
All Articles