let it: Be

ios, swift, objc

Ctrl + ↑ Later

Карринг на примере сортировки

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

Для примера возьмем массив элементов типа ChatEntity, у этого типа есть поле members, будем сортировать массив в зависимости от количества элементов в members. Напишем протокол и реализуем сортировку:

protocol SortChatProtocol {
    func sortList(members: [ChatEntity]) -> [ChatEntity]
}

class Members: SortChatProtocol {
    func sortList(members: [ChatEntity]) -> [ChatEntity] {
        return members.sort({
            return $0.members!.count > $1.members!.count
        })
    }
}

Теперь напишем каррированную функцию, которая сортирует список в зависимости от выбраной реализации, функция принимает обьект типа SortChatProtocol и возвращает функцию, которая в свою очередь принимает список с данными и возвращает отсортированый список:

func chatListSorterBy(order: SortChatProtocol)(chatList: [ChatEntity]) -> [ChatEntity] {
    return order.sortList(chatList)
}

К сожалению, на мой взгляд, в Swift 2.2 испортили синтаксис обьявления каррированных функций, теперь та же функция будет выглядеть так:

func chatListSorterBy(order: SortChatProtocol) -> ([ChatEntity]) -> [ChatEntity] {
    return { (chatList: [ChatEntity]) -> ([ChatEntity]) in
        return order.sortList(chatList)
    }
}

Осталось написать функцию, которая будет вызывать chatListSorterBy с типом сортировки и возвращать функцию с параметром [ChatEntity]:

func createChatListSorterBy(order: SortChatProtocol) -> [ChatEntity] -> [ChatEntity] {
    return chatListSorterBy(order)
}

В результате у нас получится функция sorter, которая принимает только список, так как мы уже зарание указали какую сортровку использовать.

let sorter = createChatListSorterBy(Members())
let sortedList = sorter(notSortedList)
let otherSortedList = sorter(otherNotSortedList)

Кортежи

Я окончательно и безповоротно перешел на Swift и не перестаю радоваться этому событию. Swift все-таки намного удобнее и современние, вот например в этом языке есть такая крутая штука как Кортежи (Tuples), это в каком то роде необьявленная структура для временного хранения данных, зачет это нужно? Ну например для возвращениея более одного значения из функции, например у нас есть функция, которая проверяет выиграл игрок или проиграл в зависимости от нажатой кнопки, на obj-c нам пришлось бы сделать две функции, одну для возвращения булевого значения (true – выиграл, false – проиграл), и еще одну для возвращения правильного ответа. С использованием кортежей это будет выглядеть акуратнее:

func checkWin(direction: DirectionType) -> (isWin: Bool, rightDirection: DirectionType) {
    let win = direction == self.winCaseDirection ? true : false
    return (win, self.winCaseDirection)
}

Ну и использовать дальше эту функцию будем так:

let selectedDirection = DirectionType.Right
let isWin = checkWin(selectedDirection).isWin
let rightDirection =  checkWin(selectedDirection).rightDirection
2016   Swift   tuples

Пишем игру на Swift + SpriteKit

Это первая часть из серии “Пишем игру на Swift + SpriteKit”, в которой мы создадим базовые сцены и главного персонажа, добавим физику и научим его прыгать.

Для начала создадим новый проект в Xcode и добавим главную сцену, класс с главной сценой назовем MainScene и добавим метод для общего добавления спрайтов (это можно делать и на сцене геймплея, но данный способ избавит нас от дублирования кода):

File -> New -> Project -> Aplication -> Game (Language: Swift, Game Technology: SpriteKit)

class GameScene: MainScene {
    func addSpriteNodeBy(name: String, position: CGPoint) -> SKSpriteNode{
        let sprite = SKSpriteNode(imageNamed: name)
        sprite.position = position
        addChild(sprite)
        
        return sprite
    }
}

Метод addSpriteNode принимает два параметна (название файла изображения и позицию), инициализирует спрайт, добавляет на сцену и возвращает ссылку на созданый обьект.
Теперь можно создавать сцену геймплея, которая наследует главную сцену и добавить фон при помощи написаного ранее метода addSpriteNodeBy:

class GameScene: MainScene {
    override func didMoveToView(view: SKView) {
        let background = addSpriteNodeBy("background.png", position: CGPointMake(self.frame.width/2, self.frame.height/2))
    }
}

Сцена готова, теперь можно приступить к созданию персонажа, для этого создадим новый класс под названием Character:

class Character: SKSpriteNode {
    override init() {
        let texture = SKTexture(imageNamed: "hero.png")
        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
    }
    
    init(texture: SKTexture!) {
        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
    }
    
    override init(texture: SKTexture!, color: UIColor!, size: CGSize) {
        super.init(texture: texture, color: color, size: size)
    }
 }

Так, как наш класс наследует SKSpriteNode, то мы должны обязательно переопределить инициализаторы, в строке let texture = SKTexture(imageNamed: “hero.png”) задаем текстуру нашего персонажа и передаем ее инициализатору супер класса.

Об наследовании, переопределении и инициализации можно почитать тут

Научим нашего персонажа прыгать, добавив ему физическое тело и метод jump в класс Character:

override init() {
        let texture = SKTexture(imageNamed: "FlappyBird.png")
        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
        
        self.physicsBody?.dynamic = true
        self.physicsBody = SKPhysicsBody(circleOfRadius: 20)
        self.physicsBody?.mass = 0.3
    }

    func jump() {
        let impulseX:CGFloat = 0.0;
        let impulseY:CGFloat = 10.0;
        [self.physicsBody?.applyImpulse(CGVectorMake(impulseX, impulseY), atPoint: self.position)];
    }

Теперь можно добавлять персонажа на главную сцену, в методе didMoveToView класса GameScene пишем:

var character = Character()
addChild(character)

И что-бы он не улетел в космос, добавим на сцену гравитацию, прописав следующую строку:

self.physicsWorld.gravity = CGVectorMake(0.0, -10.0);

Можете поэкспериментировать с силой прыжка, изменяя переменную impulseY и свойство self.physicsBody?.mass. Что-бы персонаж прыгал при тапе по экрану, нужно добавить обработчик событий в класс GameScene:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        character.jump()
 }

Ну и на конец, нужно добавить границы сцене, что-бы персонаж не проваливался под землю:

let borderBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0, 0, self.frame.width, self.frame.height))
        borderBody.friction = 0
        self.physicsBody = borderBody

Итак, у нас есть мир с гравитацией и персонаж который умеет прыгать, на этом пока все.

2015   game   gamedev   ios   Swift

Потрачено

Написал небольшой скриптик для подсчета времени, потраченного на просмотр фильмов. Он умеет только получать название фильма и показывать общее время.

Работает вот так: dronius.com/blog/i.php?interstellar, где “interstellar” – название фильма. Делал исключительно для себя, поэтому нет проверки на уже добавленные фильмы и вообще, полезности особой не несет, но если кому пригодится, то можно взять тут.

2015   фильмы

Из любимого за 2014 год

cover black transparent

Выбраны любимые треки из альбомов за 2014 год, хотя заслуживают внимания все произведения, поэтому советую скачать/купить альбомы полностью, ссылки ниже. Приятного прослушивания.

All shall be well – For the Ones to Whom Neither the Past Nor the Future Belong

All shall be well – I’m a Hunter, Not a Thief

Купить альбом BLAUWGEEL by All shall be well можно тут.

Release The Long Ships – I Am The Sun

Release The Long Ships – Snow

Release The Long Ships раздает свой альбом Wilderness бесплатно.

Baulta – Out Of Gravity

Jakob – Emergent

Seas of Years – Ledge

2015   музыка

Cocos2d: Эффект стекла

В новой версии Cocos2d v3.2 появилось несколько красивых эффектов, но самый удивительный – эффект стекла. Он позволяет искривлять окружающую среду, что прибавляет реалистичности. Эффект выглядит следующим образом:

Вся эта красота делается тремя строками:

CCSpriteFrame *normalMap = [CCSpriteFrame frameWithImageNamed:@"bell-normalmap.png"];
CCEffectGlass *glassEffect = [CCEffectGlass effectWithShininess:0.2 
					             refraction:0.2 
				 refractionEnvironment:background 
			         reflectionEnvironment:background 
					          normalMap:normalMap];
crystal.effect = glassEffect;

В этом примере crystal и background являются обычными CCSprites. Но наиболее сложной частью в создании стеклянного эффекта является создание карты нормалей.

Normal mapping — техника, позволяющая изменять нормаль отображаемого пикселя основываясь на цветной карте нормалей, в которой эти отклонения хранятся в виде текселя, цветовые составляющие которого [r,g,b] интерпретируются в оси вектора [x, y,z], на основе которого вычисляется нормаль, используемая для расчета освещенности пикселя.

Карта нормалей используются для имитации 3D-эффектов в 2D, прим помощи этой технологии можно создать эффект динамического освещения или преломления света, как в нашем случае. Чтобы создать кристалл из демки выше, нужны два изображения, сам кристал, который будет отображаться в игре и изображение, которое будет описывать поверхность нашего кристала:

Левая картинка будет отображаться в игре, а правая – использоваться для расчета отражения и преломления.
Создать карту нормалей можно с помощью программы PixPlant.

2015   cocos2d   gamedev

Cocos2d: Магнит

Создать эффект магнита можно несколькими способами, в моем случае нужно было притягивать кинематические объекты к динамическому. В качестве физического движка я использовал Box2d. Нужно добавить следующий код в метод update:(ccTime)dt:

if (player.isMagnet && !player.isDead) {
    for (int i=0; i<_array.count; i++) {
        CCSprite *tempSprite = _array[i];
        b2Vec2 d = b2Vec2 ((player.position.x - tempSprite.position.x), (player.position.y - tempSprite.position.y));
        d.Normalize();
        float force = 10.0f / d.LengthSquared();
        b2Vec2 F = force * d;
        [[_array objectAtIndex:i] body]->SetLinearVelocity(F);
    }
}

В этом примере массив _array содержит кинематические объекты, а player – динамический управляемый обьект. Конечно, для тел необходимо задать свойство fixtures.

По умолчанию density (удельный вес), friction (сила трения) и restitution (упругость/отдача) равны 0.2.

В случаи, когда объекты не генерируются автоматически, а используется что-то вроде тайлов или левелхелпера, можно добавить условие на проверку координат объектов. Я использую LevelHelper, поэтому коллизии обрабатываются подобным методом, в примере запускается притягивание объектов на три секунды, а затем вызывается метод stopMagnet для остановки эффекта магнита.

-(void)rocketMagnetCollision:(LHContactInfo*)contact{
    LHSprite* magnet = [contact spriteB];
    if(magnet.visible){
        [self runAction:[CCSequence actions:[CCCallBlock actionWithBlock:^{
            player.isMagnet = YES;
        }],[CCDelayTime actionWithDuration:3],[CCCallBlock actionWithBlock:^{
            [self stopMagnet];
        }], nil]];
        magnet.visible = NO;
    }
}

Для остановки всех объектов, просто передаем функции SetLinearVelocity вектор b2Vec2(0, 0)):

-(void)stopMagnet{
    NSArray *_array = [loader spritesWithTag:STAR];
    for (int i=0; i<_array.count; i++) {
        [[_array objectAtIndex:i] body]->SetLinearVelocity(b2Vec2(0, 0));
        [[_array objectAtIndex:i] body]->SetLinearDamping(0);
    }
    player.isMagnet = NO;
}

Это не очень хороший способ в плане оптимизации, но при небольшом количестве физических тел, вполне юзабелен.
P.S. Если вы не используете физические свойства, то эффект магнита для обычных спрайтов можно сделать используя простой метод CCMoveTo.

2015   cocos2d   gamedev
2014   promo

Addnum – сложение на скорость

Мой очередной проект нескольких дней, появилась идея – решил сделать, получилась простенькая головоломка на скорость.

Суть игры в том, что-бы собрать как можно больше чисел из таблицы, которые будут равны заданому значению, отображамое вверху. Из одной горизонтальной линии можно выбрать только одну цифру. В игре есть три режима сложности.

Игра скоро появится в AppStore, а пока можно посмотреть видео.

UPD: ссылка на iTunes

2014   appstore   игра
Ctrl + ↓ Earlier