Pull to refresh

Пишем простую пазл-игру для детей на iOS

Как известно, рынок игр iOS достаточно прибыльный и одной из интересных и не сложных, с точки зрения разработки, являются именно игры для детей. В этом топике я хочу рассказать, как я видел решение и создание небольшой пазл-игры. Сразу показываю, что должно получиться под конец:



Кому интересно как её сложить — прошу под кат.

Честно скажу, никаких туториалов и примеров подобных задач я не смотрел, по этому может мой способ не самый лучший, но мне показался достаточно удобным и легким в реализации, так же подойдет в качестве туториала для начинающих разработчиков.
Начнем с самых первый шагов.

Создание проекта

Для начала создадим обычный проект с одим view. Для этого открываем xCode -> Create a new xCode project -> в левой колонке выбираем Application -> Single View Application -> вписываем Product Name и остальные данные, device выбираем iPhone, не забываем поставить галочку на Use Automatic Reference Counting. Остальные чекбоксы оставляем неотмеченными.

Переходим к настройке проекта и добавления нужных нам файлов перед началом программирования

  1. В Project Navigator кликаем на файл нашего проекта. Вибираем TARGETS -> Summary и поправляем Orientations так как на изображении:
  2. Так как мы не будем работать с Interface Builder, удаляем ненужный нам файл ViewController.xib.
    Далее переходим в AppDelegate.m и ищем строчку:
    self.viewController = [[MDHViewController alloc] initWithNibName:@"MDHViewController" bundle:nil];
    

    И заменяем её на (снова же по причине не использования Interface Builder):
    self.viewController = [[MDHViewController alloc] init];
    

    Почему MDHViewController? Все дело в префиксе, который мы указали при создании проекта (у меня он MDH).
  3. Следующий наш шаг — добавления изображений к проекту. Для удобства создадим отдельную папку для них (думаю не нужно объяснять как это сделать). Клацаем на созданую папку правой кнопкой мыши и выбираем Add files to .... Выбираем нужные нам изображание и добавляем их с настройками:
    .
    Все изображения и сам проект можно скачать по ссылке:


Начало разработки

В этом проекте я буду использовать MVC паттерн.
  1. Cmd + N -> Cocoa Touch -> Objective-C class Вот мы и создали модель нашей игры.
  2. Переходим в GameModel.h. И создаем два массива данных:
    #import <Foundation/Foundation.h>
    
    @interface GameModel : NSObject {
        @private
        NSArray *_puzzlesArray;
        NSArray *_coordinatesArray;
        NSInteger _puzzlesCount;
    }
    
    @property (strong, nonatomic) NSArray *puzzlesArray;
    @property (strong, nonatomic) NSArray *coordinatesArray;
    @property (nonatomic) NSInteger puzzlesCount;
    
    @end
    

    Первый массив будет содержать наши части пазла, а второй контрольные фреймы, по которых мы будем совершать проверку перед уложением пазла. puzzlesCount — нас счетчик уложенных кусочков пазла.

    GameModel.m:
    @implementation GameModel
    
    @synthesize puzzlesArray = _puzzlesArray;
    @synthesize coordinatesArray = _coordinatesArray;
    @synthesize puzzlesCount = _puzzlesCount;
    
    -(id)init {
        self = [super init];
        if (self) {
            [self setArraysData];
            self.puzzlesCount = 0;
        }
        return self;
    }
    
    -(void)setArraysData {
        self.puzzlesArray = @[[UIImage imageNamed:@"kot_element_1"],
                            [UIImage imageNamed:@"kot_element_2"],
                            [UIImage imageNamed:@"kot_element_3"],
                            [UIImage imageNamed:@"kot_element_4"]];
        
        self.coordinatesArray = @[[NSValue valueWithCGRect:CGRectMake(285.5f, 107.0f, 50.0f, 50.0f)],
                                [NSValue valueWithCGRect:CGRectMake(211.0f, 180.0f, 50.0f, 50.0f)],
                                [NSValue valueWithCGRect:CGRectMake(78.0f, 75.0f, 50.0f, 50.0f)],
                                [NSValue valueWithCGRect:CGRectMake(84.0f, 212.0f, 50.0f, 50.0f)]];
    }
    
    @end
    
    

    Для удобства я вынес заполнение массивов в отдельный метод, который исполняется при инициализации нашей игровой модели.
  3. Переходим в наш главный ViewController.h (у меня MDHViewController.h). Присоединяем GameModel.h:
    #import "MDHViewController.h"
    #import "GameModel.h"
    

    Нам нужно добавить UIImageView, который будет представлять нам границы пазла и конечно же не забываем про игровую модель:
    @interface MDHViewController ()
    
    @end
    
    @implementation MDHViewController {
        UIImageView *puzzlesBundle;
        GameModel *gameModel;
    }
    

  4. Так как мы не используем Interface Builder добавляем метод loadView. В нем инициализируем puzzlesBundle и gameModel:
    -(void)loadView {
        [super loadView];
        puzzlesBundle = [[UIImageView alloc] init];
        gameModel = [[GameModel alloc] init];
    }
    

  5. Переходим к методу viewDidLoad:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        //даем белый фон нашему self.view
        [self.view setBackgroundColor:[UIColor whiteColor]]; 
        
        //добавляем изображение границ нашего пазла
        UIImage *image = [UIImage imageNamed:@"kot_fg"]; 
        [puzzlesBundle setImage:image]; 
        [puzzlesBundle setFrame:CGRectMake(50.0f, 20.0f, image.size.width, image.size.height)];
        [puzzlesBundle setBackgroundColor:[UIColor clearColor]];
        [self.view addSubview:puzzlesBundle]; 
        
        //добавляем четыре части пазла
        for (NSUInteger i = 0; i < gameModel.puzzlesArray.count; i++) { 
            UIImage *puzzleImage = [gameModel.puzzlesArray objectAtIndex:i];
            UIImageView *imageView = [[UIImageView alloc] initWithImage:puzzleImage];
    
            //устанавливаем рандомные координаты
            [imageView setFrame:CGRectMake(arc4random() % 250, arc4random() % 200, puzzleImage.size.width, puzzleImage.size.height)]; 
            [imageView setBackgroundColor:[UIColor clearColor]];
    
            //теги решил использовать в качестве номерации кусочков пазла, что бы позже знать с какой именно частью мы работаем и с каким контрольным фреймом сравнивать
            [imageView setTag:i]; 
            [imageView setUserInteractionEnabled:YES];
            
            UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; //добавляем возможность перетаскивания частей пазла
            [imageView addGestureRecognizer:gesture];
            
            [self.view addSubview:imageView];
        }
    }
    


    В комментариях к коду я описал основные моменты, думаю остальное должно быть понятно, кто, хотя бы немного, знаком с программированием под iOS.
  6. И основная часть, описываем наш handlePanGesture:(UIGestureRecognizer *)gesture метод:
    -(void)handlePanGesture:(UIGestureRecognizer *)gesture {
        if (gesture.state == UIGestureRecognizerStateBegan) {
    
            //при клике выдвигаем кусок пазлаповерх всех остальных
            [self.view bringSubviewToFront:gesture.view]; 
        }
        if (gesture.state == UIGestureRecognizerStateChanged) {
    
             //отслеживаем передвижение
            CGPoint dragPoint = [gesture locationInView:self.view]; 
    
             //и присваеваем нашему кусочку пазла новые координаты
            [gesture.view setFrame:CGRectMake(dragPoint.x - gesture.view.frame.size.width/2, dragPoint.y - gesture.view.frame.size.height/2, gesture.view.frame.size.width, gesture.view.frame.size.height)];
        }
        if (gesture.state == UIGestureRecognizerStateEnded) {
    
             //что бы достать значение tag, которое мы задали в viewDidLoad прибегаем к принужденному переобразованию типов
            UIImageView *imageView = (UIImageView *)gesture.view;
    
            //достаем нужный нам контрольный фрейм
            CGRect checkRect = [[gameModel.coordinatesArray objectAtIndex:imageView.tag] CGRectValue]; 
    
            //проверяем входит ли центр кусочка пазла в контрольный фрейм
            if (CGRectContainsPoint(checkRect, [self.view convertPoint:gesture.view.center toView:puzzlesBundle])) { 
    
                 //и если да, то ставим его на нужное место
                [gesture.view setCenter:CGPointMake(checkRect.origin.x + checkRect.size.width/2+50.0f, checkRect.origin.y + checkRect.size.height / 2)]; 
    
                //и блокируем для дальнейших перетаскиваний
                [gesture.view setUserInteractionEnabled:NO]; 
               gameModel.puzzlesCount++;
            }
            
            if (gameModel.puzzlesCount == 4) {
                NSLog(@"пазл сложен");
            }
            
        }
    }
    



Итак, подведем итоги. Почему я выбрал проверку с помощью контрольных фреймов и как она работает. Все очень просто, когда центр нашего перетаскиваемого кусочка в положении UIGestureRecognizerStateEnded находится в области нашего контрольного фрейма — пазл уложен и мы его оккуратно укладываем. Высота и ширина контрольных фреймов = 50.0f. Это значение можно легко поменять, чем больше эти значения — тем легче укладывать пазлы и тут у нас появляется возможность контролировать точность укладки и тем самым сложность самой игры.

Когда все пазлы правильно выложены, в консоли появится «пазл сложен».

P.S. Соглашусь, это далеко не идеальная версия и есть множество вариаций и допустимых доработок, на пример, как проверка клика именно по UIImage, а не по целому UIImageView и если клик был именно по изображению — тогда разрешаем перетаскивать, но думаю это каждый сам сможет добавить, если в этом будет подребность.

P.P.S. спасибо за внимание, ссылка на репозиторий. Архив с изображениями.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.