Git and unity and google drive oh my!

When developing anything it is advised to use a versioning system. At my workplace we use SVN, but I choose to use git for personal projects, and not only because you can store the code on github, it allows for versioning locally, while not connected to the internet.
I’m going to teach you the basics of using git with unity and google drive together, but even if you don’t use unity, you might find this useful.

What is Git?

Git is a versioning system. It allows you to incrementally commit changes, and it stores all the changes you’ve made. If you’ve ever used google docs, it does a similar thing. You can click “All changes saved to drive” and you’ll be shown the version history.
Git is a lot more powerful than that, and has many features for merging changes by different people and collaboration, but as a team of one, all I need is the versioning and the ability to go back.

Never lose any work

If you’re developing open source or public software, using github as your repository is a fine choice, but if you’re developing a private project, you’ll have to pay to store it remotely. That is why I chose to make a local repository for my private projects. I personally use TortoiseGit which has a nice shell overlay and powerful context menu that saves all the hassle of learning and typing commands.
However, storing locally means that if your hard drive dies on you, you’ve lost your work, and we can’t have that!
I personally use google drive for my cloud storage needs. Up until today, I would use TortoiseGit to create a repository inside the google drive folder, and then work in that folder when developing in unity.
The problem is, and I’ve been encountering this for a while, google drive locks the files when trying to back them up into the cloud, making unity throw a bunch of errors in the process.
Furthermore, google drive will try and update files as you change them, every step of the way, not very efficient.

Bare repository

Git has a distinction between a repository you work in, and a repository made only for storing information, a bare repository.
When using Tortoise Git, create a folder with the name of your project inside the google drive folder.
Right click the folder, and chose Git create repository here.

When the dialog pops up, check make it bare.
Now create your unity project outside of the google drive.

After you create your project folder, It’ll look something like this.

Right click the parent folder, and choose Git create repository here again, but this time leave Make it Bare unchecked.
Unity creates a lot of cached files inside the project which aren’t required to be backed up. If you will lose them, and open the project in unity, unity will recreate them. To not commit those files, you’ll need to create or download a .girignore file to tell git not to mind certain files. Here is mine, feel free to use it.
Once you have the .gitignore file in place, you’re ready to commit locally.
The first step is to add all the files to be versioned, right click the parent folder, and click Tortoise Git -> Add.

Go ahead and add all those files.
After the files have been added, git already suggest committing the files, click commit.

Git won’t let you commit anything without a message, so go ahead and type something in. You generally want it to be informative, so I just say first commit, or bare project.
After clicking commit, the changes have been stored locally, but not in the bare repository.
The next step would be to push the changes to the bare repository.
Right click, Tortoise Git -> Push.
Under destination, click Manage.
Under remote, fill in an arbitrary name (no spaces), and under URL paste the path to your repository inside your google drive.
Then click Add new/save.

You should have something like this.
Close it, click ok on the push dialog and it will push the changes to the repository!

If in the future you lose your working copy, you can right click anywhere and click Git Clone. Point it to the bare repository, and it will reconstruct everything.

That’s it. You now have Git working with google drive and unity, without anything stepping on anything else’s toes.
Hope that was helpful.

If you like this post, you may enjoy my two design posts about my steemitjam game.
Theme interpretation: Protect it
Initial design: Settling in

Advertisements

Gamedev blog 1: Brewing an idea

If game development was my day job, my process would be completely different. Since it is a hobby, my process is very relaxed, and takes a very long time to manifest. And by very long time, I mean years. This means I have quite bit to say about it!

In the beginning there were cards

I started with the core thought of bringing deck building mechanics into video games. At the time, deck building games like Dominion and Ascension were just deck building as a game mechanic, with nothing else. Play cards, buy cards, repeat. It’s a very fun mechanic, that allows for engine building and growing in power. Allows for strategy and tactics. I always felt like these mechanics could really shine in a more complex system. These days there are quite a lot of games that incorporate deck building with other game systems, even some video games.
Personally, I like developing video games, mostly because of their solo nature requiring less playtesters than physical board games. So I started thinking about deck building and video games.


Dominion – the game that started it all

I’ve spent a lot of time thinking about deck building games, deconstructing and finding what they mean for the player. Essentially using deck building mechanic in another game system, one that would use static number of abilities like an RPG, gives us two advantages.
First, it allows for a very wide range of abilities, while not overwhelming the player with choice, and even more importantly, not allow for one ability to overshadow all the rest. By not having all the abilities, presented as cards, available at all times, you force the player to use a much wider range of abilities. But you don’t overwhelm the player with many abilities all available at the same time. Furthermore the discard and redraw mechanic allows for a built in and variable cooldown clock on those abilities.
Second, deck building allows for character growth in a much more abstract manner. Instead of leveling up, skill trees, and stats, your character can add or remove cards in their deck to become more powerful. Both simplifying the system, and allowing for much more diversity on character development paths.

Solving problems in thought experiments

A roguelike/roguelite with deck building at its core seemed like a natural progression. Allowing players to grow in power by acquiring cards, giving players even more unique experiences as card combinations unlock powerful engines. This idea went through many iterations in my head, with lots of problems I needed to solve. Some of the more interesting ideas and problems I’ve faced:

If I allow a use of a card to be automatically replaced, then a character exploring a dungeon can always cycle the cards until they have a “perfect” hand before entering a danger zone. This is a problem not because it is too powerful, but rather because the dominant strategy will not be fun. This problem can be solved by attaching a cost to playing a card, such as paying food, which is common in many roguelikes. However it might still be a good strategy to cycle some cards and pay the food, and I was unsure if that would be fun.
Monster Slayers, a digital deck building game, solved this by separating the dungeon into combats, and drawing a fresh hand for every combat. This is a solution that prevents a lot of design space for cards that have meta effects, such as teleportation between rooms, since cards are not available when not in combat.


Monster Slayers – A digital deck building game

Considering Risk & Reward

A risk reward system that allows removing cards from the deck in an inherently interesting fashion. First, I say that cards in hand are health. When a character is attacked and takes damage they discard cards, when they can’t discard cards when they’ve taken damage, they die. This also allows for interesting design of cards in hand that when discarded have an effect, such as a reposte, or cards that generate temporary cards in hand as shields.
Now after that, I can say that under certain conditions, instead of discarding a card from damage, it is removed from the deck completely. Thinking back to having a risk reward system, then I want these conditions to be mostly voluntary. My idea was a darkness that approaches from behind, if the character is inside the darkness, then discards are removed instead. Then characters can decide to stay back, enter the darkness, and remove cards. Of course, staying in the darkness is a risk, as enemies are stronger, and may kill the character!

Game On! Utilizing the medium

Lastly, it was important to ask myself the question, what can a video game do with deck building that a physical game cannot, or has a hard time doing? Some answers include cards that have memory, and change based on how you use them, for example a bloodthirsty card that grows more powerful the more enemies you kill with it. Random effects are much easier to generate programmatically than using dice, or other physical randomizers. And playing around with draw chances, instead of simply drawing from a deck of cards, and shuffling.

Join me next time, as I start to tackle this project. Slowly.

Roguelike Core – Open source code base for roguelike in unity

Background

When searching the web for roguelike unity all you can find is unity’s own roguelike2D tutorial. While the tutorial did teach me a few things, and is overall very good, there are a few things that turned me off, and made me look elsewhere for my roguelike development. Most notably is their use of a coroutine to execute an action, and guessing how long it will take to resolve.

While searching for knowledge of how to implement better roguelike base code I came across A turn based game loop by Bob Nystrom. This comprehensive article contained everything that I needed to write my own implementation of a roguelike game loop. Since it is entirely based on someone else’s work, I immediately wanted to make this open source so everyone could benefit from it, like I benefitted from this article.

Introduction

Everything I made for this project can be found on github. What I want to do here is go over the core of the code. My aim is to make you familiar with it so you an easily use it yourself and extend it for your own projects.

I will not go into the reasoning and design of the code, since it is explained in full in Bob Nystrom’s article.

GameLoop.cs

The game loop is in charge of going over each actor and ask them which action they would like to perform. This is done in the update method, and no more than one actor will perform an action per frame. Even if you have no animations at all, this is fast enough that the player won’t even notice any wait.

private void PerformAction ()
{
    currentAction = GetCurrentActor().GetAction();
    if (currentAction == null) return; //Should only happen for player actor
    while (true)
    {
        ActionResult result = currentAction.Perform();
        if (result.Succeeded)
        {
            if (!GetCurrentActor().HasEnergyToActivate(currentAction.EnergyCost))
            {
                Debug.LogError(currentAction +  "Perform should check if actor has sufficient energy, and return an alternate if it does not. Defaulting to rest action.");
                currentAction = GetCurrentActor().GetRestAction();
            }
            GetCurrentActor().SpendEnergyForActivation(currentAction.EnergyCost);
            break;
        }
        currentAction = result.Alternate;
    }
}

This is the interesting part of the game loop, the action selection. This is pretty much a direct implementation from the article. What you need to know is that action.Perform() is in charge of checking if the actor has enough energy to perform the action, but if it does not check it properly, the game loop double checks this and defaults to a rest action. Do not rely on the game loop to do this check for you, as it means that action fallback will always be to rest action, even if a cheaper action is possible.

The game loop will then spend the energy if it is successful, and break the loop, which will finish the frame.

The last thing you need to know is that Actors are in charge of registering and unregistering themselves from the GameLoop. I’m aware this is coupled code, but it is the simplest way to make sure you don’t accidentally double up on Actors.

BoardManager.cs

BoardManager is in charge of storing the static board, along with the positions of all the Actors, and if you’ll want to add pickups and other components, here is the place to do so.

The board itself is a grid of chars, currently P denotes passable. I imagine pretty much anyone using this code will change it, so I didn’t bother making it terribly pretty. What you need to know is that IsPassable only considers static terrain on the grid, and will ignore all other Actors on the board. IsOccupied and GetOccupied checks if the spot is occupied by another Actor. This is mostly because of the way I use the A* pathfinding, up in the next section.

BoardManagerAStarCost.cs

This is the implementation of AStarCost that uses the Board Manager to find out if a spot is passable. To learn more about this implementation of A* you can see my article on how I added A* to unity’s roguelike tutorial project.

The implementation also works with diagonal movement, but this is currently disabled in AStarNode2D.cs allowDiagonal=false. Feel free to turn it on, but be aware you’ll have to modify the action cost of movement.

This implementation of A* finds a path between two points, including the starting position and end position. So if an Enemy is at 0,0 and the player is at 0,2, and the Enemy is finding a path to the player, it will return [(0,0),(0,1),(0,2)]. And this is why it needs to consider the spot the player actor is on as passable. This is also important because of the way action delegation works. The enemy will try to attack 0,1 and if it is empty it will move there. Then it will try to attack 0,2 and since the player is there, it will attack.

Note that I recalculate the pathfinding on every step. If this becomes too computationally intensive, you could cache the result, and first check if the player is still on the path, and then start finding the path from the end.

Action.cs

The base Action class which you will need to extend in order to add more actions to your game. The only method you need to overload is Perform().

When Perform() is called, you should check if the actor has enough energy to perform the action, and if not, which action would be a good alternative. Usually, if you don’t have enough energy, you’ll want to rest, which regains 1 energy. However, in the case of an attack action, I first check if I can perform the action, and if not I move instead. This is because if a move action is cheaper than an attack, then I might be able to perform it even if I don’t have energy for an attack.

The perform action should either change the state to executing, and then change to finished during the update process, or change it immediately to finished, like in rest action.

When you return a failed action result, you attach an alternate action to it. The current delegation is Attack -> Move -> rest. Make sure everything eventually defaults to rest, as it’s the action you can always perform to regain 1 energy.

RestAction.cs

When you add this script to an Actor, you can change the amount of energy it would regain from a rest. I suppose you could do some interesting things with changing this on the fly. Otherwise, it could be used to make different increments of the energy recovery loop.

AttackAction.cs

The attack action requires a weapon component to be attached to the Actor. It delegates almost all of the logic to the weapon, and as such is very flexible.

Weapon.cs and LinearWeapon.cs

A weapon needs to implement two methods. AcquireTarget(IntVector2 direction), which which takes a direction as input, and returns whether or not that attack direction is valid. And UseWeapon() which applies the attack.

Linear weapon will check if there is any destructible target in the direction and within range. And UseWeapon will apply its damage to the first destructible object in it’s path. So a LinearWeapon script can be used for bump attacks of any kind, including ranged weapons.

Destructible.cs

Don’t forget to add this to all Actors that can be dealt damage. This class also automatically notifies all other scripts that they should Unregister using SendMessage. If you wanted to implement a death animation, the Die() method would be the place to do so.

EnemyActor.cs

EnemyActor is very simple. I uses A* to find the path to the player, then tries to attack everything in it’s way, delegating to movement if there is nothing to attack. Since the delegation happens in the attack action, you might want to implement a different enemy attack action if you want enemies that cycle between movement and attacks consistently.

PlayerActor.cs

Lastly, the player actor. The first thing we do is check if it has enough energy to take an action, otherwise we rest. This is because we only have one cost and one type of action available. If you wanted, you could allow the player to take a rest action by choice each loop. That way they could make many different actions, but waiting 2-3 loops between each movement is tedious, so beware. If you allow resting by choice, you’ll also probably want to make a maximum energy cap, otherwise players could store up on energy and then go on a rampage.

Final words

Hope you find this useful, and I’ll be happy to be notified if you use this code in any of your projects!

The code is completely free to use, and under the public domain Creative Commons 0 license. Free free to share change, use, and even claim you wrote it, though that would be a dick move. If you want to give attribution, please link to http://www.timeshapers.com

Follow me to see how I develop this core into my own roguelike.

Adding A* pathfinding to Unity2D roguelike example

The purpose of this tutorial is to add A* pathfinding to Scavengers 2, the Unity 2D roguelike example.

The 2D roguelike project is the basis upon which this tutorial is built, and is required to follow through. If you’re unfamiliar with the project, you can follow the tutorial. Make sure you have it imported into a project.

You will also need my modified code for the A* pathfinding can be found here. The A* code is a modified version of the code that can be found here.

If you have played Scavengers 2, you may have noticed the enemies are quite stupid. Here is the code governing the enemy movements, in Enemy.cs:

public void MoveEnemy ()
{
    int xDir = 0;
    int yDir = 0;

    if(Mathf.Abs (target.position.x - transform.position.x) < float.Epsilon)

    yDir = target.position.y > transform.position.y ? 1 : -1;

    else
    xDir = target.position.x > transform.position.x ? 1 : -1;

    AttemptMove <Player> (xDir, yDir);
}

As you can see from this code, the enemy checks if the player is in the same column. If it is, then it will move up or down towards the player. If it isn’t, it will move left or right towards the player.
In case the player is 4 down and 1 to the left of the enemy, and to the left of the enemy is a wall, the enemy will not move, as it will attempt to move left, fail due to the wall, and end its movement.

Now there are many ways to improve this logic, a simple one would be to try and move down in the event that left is blocked, but in that case the enemy still wouldn’t move if there was another wall right below it. We would really prefer if the enemy moved around the wall even in that position. Here is where A* can help. A* will find the shortest path from the enemy to the player, and walk that way.

The method I present here is not ideal. I will be using a lot of linecasts in order to find a good path. This is computationally intensive. However, this is also the easiest way to add A* pathfinding to Scavengers 2, with the least modifications to the project.

The ideal way would be to record the position of every entity and wall, and then check the new positions against a record rather than line casting.

The files are:
AStar.cs – The core logic of the pathfinding, and the class you call to start.
AStarNode.cs – Define the core of a path node, extend if you want to implement 3D pathfinding for example.
AStarNode2D.cs – The 2D implementation of AStarNode.cs.
AStarCost.cs – A base class to calculate cost of moving between two points and if it is at all possible, extend this if you want a different way to use a different method of checking for passable nodes. The aforementioned recorded positions, rather than linecasting, for example.
LineCastAStarCost.cs – A raycast implementation of checking if a position is accessible.
Heap.cs – A heap data structure, generic and unrelated to pathfinding.
SpaceConstants.cs – The constant cost of orthogonal and diagonal movements.

I will not go over the entire code, but rather only go over important parts. I encourage you to read over the entire code on your own.

AStar.cs has two methods you should pay attention to:

public void findPath () {
    openList.Add (startNode);
    while (openList.Count > 0) {
        AStarNode current = (AStarNode)openList.Pop();
        if (current.isGoal()) {
            recordSolution(current);
            break;
        }
        processSuccessors(current.getSuccessors());
        closedList.Add (current);
    }
}

This method manages the opened and closed lists using the heap. It iterates over the open list, and calls getSuccessors, which essentially populates all the nodes you can move to from that node. If the node has reached the goal, then the solution has been found.
The heap is sorted by the heuristic cost of the nodes, the heuristic cost is the cost of reaching the point of the node so far, plus the distance to the goal. This way we explore the node with the best potential first.
The cost calculation can be found in AStarNode2D.cs CalculateGlobalCost(). In the case of no diagonal movement, the calculation is manhattan distance:

return Mathf.Abs(xd) + Mathf.Abs(yd);

And in the case of diagonal movement, we use euclidian distance:

return Mathf.Sqrt((xd*xd) + (yd*yd));

The original author recommended a different method of calculating diagonal distance:

return Mathf.Max(Mathf.Abs(xd),Mathf.Abs(yd))*10;

But honestly, I don’t understand it. It is commented out in the code, feel free to test it.

The closed list is a list of all the nodes that already has children. A node in the closed list does not need to be iterated over again. A node in the open list needs to be iterated over.

private void processSuccessors(List<AStarNode> successors) {
    foreach (AStarNode successor in successors) {

        AStarNode nodeOpen = getFromHeap(successor, openList);
        if (successorWorseThanExisting(successor, nodeOpen)) {
            continue; //Throw away
        }

        AStarNode nodeClosed = getFromHeap(successor, closedList);
        if (successorWorseThanExisting(successor, nodeClosed)) {
            continue; //Throw away
        }

        openList.Remove (nodeOpen);

        closedList.Remove (nodeClosed);

        openList.Add (successor);
    }
}

This method is called after the children nodes are added to the node. If you allow only orthogonal move, that would be up, down, left and right. The method will iterate over all successor nodes of a node and getFromHeap any node that represents the same position as the current successor (means that it was reached through a different path). If the cost of reaching that point is worse than the current successor, we throw the current successor away. If the current successor is better, then we remove the old identical nodes from both lists, and add the current one to the open heap, so we can explore it further.

Next I’d like to go over LineCastAStarCost.cs, since this is where some of the scavengers 2 specific code happens, and you’d want to write your own AStarCost in your projects.

public override float getCost(int toX, int toY, int fromX, int fromY)
{
    if (toX != fromX && toY != fromY)
    {
    //Diagonal, so check if can move in both orthogonal directions.
        if (isPassable(toX,fromY,fromX,fromY) && isPassable(fromX, toY, fromX, fromY))
        {
            return SpaceConstants.GRID_DIAG;
        }
    }
    else if (isPassable(toX,toY,fromX,fromY))
    {
        return 1;
    }
    return -1;
}

This method is fairly generic. In case of diagonal movement (movement in both X and Y), we check to see if a movement only in X and only in Y is possible. This is very dependent on the game. In some games, you would want to allow diagonal movement, even if an adjacent tile is blocked. In others, you may want to force two steps around a corner. The cost of a diagonal move is square root of 2.
If it is not a diagonal move, then simply check if it is possible to move in that direction.
If a move is not possible, return -1.

private bool isPassable(int toX, int toY, int fromX, int fromY)
{
    Vector2 start = new Vector2(fromX, fromY);
    Vector2 end = new Vector2(toX, toY);
    RaycastHit2D hit = Physics2D.Linecast(start, end, _blockingLayer);
    return hit.transform == null;
}

Here you can see the method of testing if the movement is possible. Similarly to how MovingObject.cs does the move check, we’re using a linecast. This is why you will notice later we will disable the enemy and target collider before pathfinding. If we do not disable the enemy collider, no path will be found, as the enemy position will always be unreachable.

Lastly, we would like to make changes to Enemy.cs, in order to use the pathfinding. The method we would like to change, is the method we saw earlier, MoveEnemy(…), that governs the Enemy movement logic.

public void MoveEnemy ()
{
    int currentX = Mathf.RoundToInt(transform.position.x);
    int currentY = Mathf.RoundToInt(transform.position.y);
    GetComponent<BoxCollider2D>().enabled = false;
    target.GetComponent<BoxCollider2D>().enabled = false;
    AStar astar = new AStar(new LineCastAStarCost(blockingLayer), currentX, currentY, Mathf.RoundToInt(target.position.x), Mathf.RoundToInt(target.position.y));
    astar.findPath();
    AStarNode2D nextStep = (AStarNode2D)astar.solution[1];

    //GetComponent<BoxCollider2D>().enabled = true;
    target.GetComponent<BoxCollider2D>().enabled = true;

    int xDir = nextStep.x - currentX;
    int yDir = nextStep.y - currentY;

    AttemptMove <Player> (xDir, yDir);
}

We would like to keep the AttemptMove<Player>(xDir, yDir) at the end, but replace all the rest.
First we would round the position of the enemy to ints. We do not use casts because a 0.999 would be cast down to 0. Then we remember to disable both the collider of the enemy and the collider of the target.
We instantiate a new AStar object, giving it a LineCastAStar, with our blocking layer, a start and and end position for our pathfinding algorithm.
We tell it to find a path, and then simply get the 1 index of the path found. We don’t get 0 index, because that would be the current position of the enemy. In more complex games you may want to make a check here to see if the size of the path is at least 2 before moving, but in Scavengers 2 it is nearly impossible for there to be no path between an Enemy and the player.
Lastly, we subtract the current position of the enemy from the next position, in order to pick a direction to move in.

That’s it, your enemies should now be much smarter, and since they don’t walk into walls, much more predictable.
If you’d like to allow diagonal movement, have a look at AStarNode2D.cs, and simply change allowDiagonal to true. You can also look over the calculateGoalEstimate and GetSuccessors methods to see what changes when you change this boolean.

Again, credit to Sune Trudslev, from which I took the base code, and learned A*.

A look into difficulty in Diablo and Binding of Isaac

I am a long time Binding of Isaac player, and it is one of my all time favourite games. I also recently picked up Diablo III on sale, I needed the distraction, and I knew Diablo would deliver.

A friend recently linked me to a post on Diablo about defense, offense and unavoidable damage. The basic premise of the post was that if the difficulty is ever increasing, then damage grows to infinity and therefore any defense is meaningless. This got me thinking about the system in diablo, and provoked a short discussion with my friend.

In Diablo, skill is used primarily to avoid damage. Dodging projectiles, like in a bullet hell game, to avoid damage for as long as it is needed to kill the enemy. Because of the nature of the abilities you use in game to apply damage, skill cannot be used, beyond a certain point, to apply significantly more damage.

Stats can be broken down into what they actually mean in gameplay:
Defense (health plus resistances) is the amount of mistakes you can make before you die.
Enemy health divided by DPS is the amount of time you need to not make more mistakes than you’re allowed by your health.
Health regeneration is the amount of mistakes you reclaim over time.

This means that increasing the difficulty through those stats merely serves to make fights longer, keeping the player on their toes for longer stretches of time while they avoid damage. This is excellent, until you pass a certain threshold where it becomes a chore to remain in the fight for so long. I think that threshold is somewhere around 3 minutes. Up until three minutes it is more interesting as the the battle grows longer, I could see myself trying to fight 3 minute battle without taking any damage. But as you go over three minutes it becomes simply less and less enjoyable. If you can avoid damage for 3 minutes, you can probably do it for 5, 9, even 20 minutes. If you misstep, you probably simply got bored.

Now since there is no way, using skill, to increase the amount of damage you apply (again, beyond a certain point) since the only reliable way to increase damage is better equipment, you’re left with just finding the “correct” difficulty, the one where it takes about 3 minutes tops to kill monsters, which is entirely up to random drops. This is, of course, provided you’re competent enough to survive three minutes, if you’re a weaker player, it is a matter of finding the spot where you feel challenged, which is always going to be fun.

My friend then brought up Binding of Isaac, knowing I like the game. And claimed, initially, that the system was essentially the same. But it is not. Due to the nature of the attacks in Binding of Isaac, and sometimes the nature of the enemies, skills can indeed be applied to increase damage output. The fact that shots only fire orthogonally, unlike diablo where most everything is point and click, means that skill can be applied to increase your DPS. The Knights in Binding of Isaac only take damage from the back, taking skill to damage them.

Sure, there are items in the game which allow you to ignore one skill requirement or another in fights, but those are only available to you for the span of an hour of a game, after which point you start a new game, where you might have to apply all your skill to win. Those serve both as a relief from constant difficulty, and as a means to allow “weaker” players to win from time to time.

In conclusion, I think the inability to use skill to apply damage in Diablo hurts the game significantly. I also think that for most players, this is absolutely fine.

Pixel perfect collision in Unity 2D

I’m going to give a short overview of how pixel perfect collision works, and how I implemented it in Unity using C#.
The source files and usage are at the bottom.

Overview:
Pixel perfect collision assumes that sprites are never rotated.
1. Start with checking if the two rectangles of the sprites are colliding, if they are not, there is no point in testing per pixel collision. This is a simple AABB collision test. For this, we use the Bounds2D class. Some code from it:

public bool overlaps(Bounds2D other) {
    return isHorizontalOverlap(other) && isVerticalOverlap(other);
}

public bool isHorizontalOverlap(Bounds2D other) {
    return !(x > other.x + other.width || other.x > x + width);
}

public bool isVerticalOverlap(Bounds2D other) {
    return !(y > other.y + other.height || other.y > y + height);
}

This code checks if other is on the left or right of this. If it is, then it’s not colliding.
Then does the same for top and bottom.

2. We need the intersection of the two rectangles, to know which section of the texture we need to compare:

public Bounds2D intersection(Bounds2D other) {
    Bounds2D ret = new Bounds2D();
    ret.x = Mathf.Max(x, other.x);
    ret.y = Mathf.Max(y, other.y);
    ret.width = Mathf.Abs(ret.x - Mathf.Min(x+width,other.x+other.width));
    ret.height = Mathf.Abs(ret.y - Mathf.Min(y+height,other.y+other.height));
    return ret;
}

We then need to translate that intersection to a local position of the specific texture:

public Bounds2D toLocal(Bounds2D global) {
    Bounds2D ret = new Bounds2D();
    ret.x = global.x - x;
    ret.y = global.y - y;
    ret.width = global.width;
    ret.height = global.height;
    return ret;
}

3. We want to get the pixel data from the Texture2D, using the intersection we found:

private void getBits() {
    //Intersection of the two bounds in global space.
    Bounds2D intersection = boundsA.intersection(boundsB);
		
    //Intersections in local space.
    Bounds2D intersectionA = boundsA.toLocal(intersection);
    Bounds2D intersectionB = boundsB.toLocal(intersection);
		
    bitsA = getBitArray(intersectionA, a.collisionMask);
    bitsB = getBitArray(intersectionB, b.collisionMask);
}
	
private static Color[] getBitArray(Bounds2D section, Texture2D texture) {
    return texture.GetPixels(section.x, section.y, section.width,section.height);
}

One important thing to note, is that you’ll need to set the texture to readable in order to access the pixel data. You do this by changing a texture to advanced, and checking the readable checkbox.

4. Now that we have the pixel data, we simply compare the alpha of every pixel:

private bool perPixelCollision() {
    getBits();
    for (int i = 0; i < bitsA.Length; i++) {						
        // If both colors are not transparent (the alpha channel is not 0), then there is a collision
        if (bitsA[i].a != 0 && bitsB[i].a != 0)	{
            return true;
        }
    }
    return false;
}

Usage:
Here you can download the full code. The usage is fairly simple.
SpriteCollider is the only MonoBehavior. you can either place it onto an object with a Sprite Renderer and it will get the texture automatically.
Or you can use a custom mask by placing a texture into the collisionMask field in the inspector.
If you uncheck pixelCollision, it will only check bounds, which is good if your sprite is a full rectangle.

This collision does not work with Unity’s physics engine, and does not trigger any collision methods. You have to manually check for collisions where you want to.

Don’t forget you’ll need to set the texture to readable in order to access the pixel data. You do this by changing a texture to advanced, and checking the readable checkbox.

You can download the files here

Credit to http://rylgh.com/pixelshitter/ from whom I learned how to make pixel perfect collision.