Ryan Nielson

The personal site and blog of Ryan Nielson.

© 2014. Ryan Nielson All rights reserved.

2d Collisions in Unity

In Unity 4.3, 2D physics were added to ease the creation of two-dimensional games. Although the 2D physics behave very similarily their 3D counterpart, there are some important differences regarding the messages that are sent to game objects when collisions occur.

A matrix is provided by Unity that lists what messages are sent to an object when collisions occur, but this is only for 3D physics. Though the 2D physics messages are similar, there are some important differences. The matrices below assumes you're using the 2D variants of colliders and rigidbodies.

Collision detection occurs and Collision2D messages are sent
Static Rigidbody Kinematic Rigidbody Static Trigger Rigidbody Trigger Kinematic Rigidbody Trigger
Static X
Rigidbody X X X
Kinematic Rigidbody X
Static Trigger
Rigidbody Trigger
Kinematic Rigidbody Trigger
Trigger2D messages are sent upon collision
Static Rigidbody Kinematic Rigidbody Static Trigger Rigidbody Trigger Kinematic Rigidbody Trigger
Static X
Rigidbody X X X
Kinematic Rigidbody X
Static Trigger X X
Rigidbody Trigger X X X X X X
Kinematic Rigidbody Trigger X X

These tables were created after some quick tests when developing my game. If any information is incorrect please feel free to message me on Twitter @RyanNielson or comment on this post.

Easy Route Localization in Laravel using Filters

Laravel already handles localization, and its filter system can make it quite easy to add content localization for any number of languages. This post will describe a simple approach to localization using route filters.

For testing this, I've created the following test translation files and view. I also made sure the configuration settings match the default localization that I want. I've commented the code below to describe what file the code is in. It uses the built-in Laravel functionality to handle different localization strings based on locale and I also use the trans helper function in the view to fetch the localized strings.

// app/lang/en/localization_test.php
return array(
    'title' => 'English title',
    'subtitle' => 'English subtitle',
);

// app/lang/fr/localization_test.php
return array(
    'title' => 'French title',
    'subtitle' => 'French subtitle',
);

// app/views/localization_test.php
<h1>{{ trans('localization_test.title') }}</h1>
<h2>{{ trans('localization_test.subtitle') }}</h2>

// Ensure the following configuration values are set in app/config/app.php
'locale' => 'en'
'fallback_locale' => 'en'

The first thing we have to do is create our filter. In app/filters.php add the following code. It checks the input for the lang value, and sets the locale for Laravel to use when handling localization. In our case, the lang value is retrieved from the URL. For any route that we wish to add localization, we simple have to make sure it runs through this filter first.

Route::filter('localization', function() {
    App::setLocale(Route::input('lang'));
});

Now within app/routes.php it's just a matter of ensuring that we use the filter for any routes we want localized. Below is what the routes.php file currently looks like for testing purposes. If you want to localize all routes, it's just a matter of wrapping them using Route::group like in the following example.

Route::group(['prefix' => '{lang?}', 'before' => 'localization'], function() {
    Route::get('/', function() {
        return View::make('localization_test');
    });
});

The result of this is that visiting urls like yoursite.com/en or yoursite.com/fr will render the view with the correct translation strings. Also, because we set locale and fallback_locale, visiting yoursite.com will automatically show the English translations.

A side effect of using the filter like this is it's just a matter of prefixing any endpoints with the locale to get translated routes, as long as they're in the Route::group above. URLs like yoursite.com/fr/blog, yoursite.com/en/blog, and yoursite.com/blog will all show the same page with the expected translations when added to this group.

This method could also be used to add localized routes for specific users with just a few tweaks. It's just a matter of removing the lang prefix from the route group, and in the filter we just need to check the user's localization setting which could be stored as an attribute on the user model. You could also examine the request headers and try to get their locale from there.

// app/routes.php
Route::group(['before' => 'localization'], function() {
    Route::get('/', function() {
        return View::make('localization_test');
    });
});

// app/filters.php
Route::filter('localization', function() {
    App::setLocale(Auth::user()->locale);
});

Hopefully this has been helpful. The Laravel documentation has much more detailed information on localization and routing which you should read if you haven't already. If anyone has any other interesting ways to do localization please feel free to message me on Twitter @RyanNielson or comment on this post.

Making a Target Tracking Orthographic Camera in Unity

I'm working on a new prototype of a 2D local multiplayer space game. I wanted to make a camera system that allowed for larger levels, while keeping all players in view. Another requirement was that the camera zoomed in and out depending on player proximity to create a more dynamic view of the action.

I'm working in Unity, so the code below is in a MonoBehaviour class. It should be attached to a GameObject with the Camera component. This could be improved in many ways, but it served its purpose for my prototype. Below is a short gif that showing the camera tracking three targets (green and red), the code for the component, and short explanations of each method at the end of this post.

Target Tracking Orthographic Camera

using UnityEngine;

public class TrackTargets : MonoBehaviour {

    [SerializeField] 
    Transform[] targets;

    [SerializeField] 
    float boundingBoxPadding = 2f;

    [SerializeField]
    float minimumOrthographicSize = 8f;

    [SerializeField]
    float zoomSpeed = 20f;

    Camera camera;

    void Awake () 
    {
        camera = GetComponent<Camera>();
        camera.orthographic = true;
    }

    void LateUpdate()
    {
        Rect boundingBox = CalculateTargetsBoundingBox();
        transform.position = CalculateCameraPosition(boundingBox);
        camera.orthographicSize = CalculateOrthographicSize(boundingBox);
    }

    /// <summary>
    /// Calculates a bounding box that contains all the targets.
    /// </summary>
    /// <returns>A Rect containing all the targets.</returns>
    Rect CalculateTargetsBoundingBox()
    {
        float minX = Mathf.Infinity;
        float maxX = Mathf.NegativeInfinity;
        float minY = Mathf.Infinity;
        float maxY = Mathf.NegativeInfinity;

        foreach (Transform target in targets) {
            Vector3 position = target.position;

            minX = Mathf.Min(minX, position.x);
            minY = Mathf.Min(minY, position.y);
            maxX = Mathf.Max(maxX, position.x);
            maxY = Mathf.Max(maxY, position.y);
        }

        return Rect.MinMaxRect(minX - boundingBoxPadding, maxY + boundingBoxPadding, maxX + boundingBoxPadding, minY - boundingBoxPadding);
    }

    /// <summary>
    /// Calculates a camera position given the a bounding box containing all the targets.
    /// </summary>
    /// <param name="boundingBox">A Rect bounding box containg all targets.</param>
    /// <returns>A Vector3 in the center of the bounding box.</returns>
    Vector3 CalculateCameraPosition(Rect boundingBox)
    {
        Vector2 boundingBoxCenter = boundingBox.center;

        return new Vector3(boundingBoxCenter.x, boundingBoxCenter.y, camera.transform.position.z);
    }

    /// <summary>
    /// Calculates a new orthographic size for the camera based on the target bounding box.
    /// </summary>
    /// <param name="boundingBox">A Rect bounding box containg all targets.</param>
    /// <returns>A float for the orthographic size.</returns>
    float CalculateOrthographicSize(Rect boundingBox)
    {
        float orthographicSize = camera.orthographicSize;
        Vector3 topRight = new Vector3(boundingBox.x + boundingBox.width, boundingBox.y, 0f);
        Vector3 topRightAsViewport = camera.WorldToViewportPoint(topRight);

        if (topRightAsViewport.x >= topRightAsViewport.y)
            orthographicSize = Mathf.Abs(boundingBox.width) / camera.aspect / 2f;
        else
            orthographicSize = Mathf.Abs(boundingBox.height) / 2f;

        return Mathf.Clamp(Mathf.Lerp(camera.orthographicSize, orthographicSize, Time.deltaTime * zoomSpeed), minimumOrthographicSize, Mathf.Infinity);
    }
}

LateUpdate() runs every frame, and it sets the values for the camera. It retrieves a bounding box that contains all targets, gets the camera position based on the bounding box, and sets the camera's orthographic size based on the bounding box.

CalculateTargetsBoundingBox() does exactly as it's name explains, and returns a Rect that represents a box that fits all the targets. This is calculated by iterating through the targets array and retrieving the minimum and maximum x and y positions from those GameObjects. It also adds a padding to the bounding box so that the camera view will adjust before the targets hit the edge.

CalculateCameraPosition() is a simple method that simply returns the center of the target bounding box to use as the new camera location.

CalculateOrthographicSize() figures out the new orthographic size of the camera, based the bounding box the contains all the targets. Since the viewport of the camera and the bounding box are both centered and aligned rectangles, we can simply use one point of the bounding box to get the new orthographic size. Using camera.WorldToViewportPoint(topRight) we get the top right point of the bounding box in Viewport form which basically gives us a ratio to work with. We then use this number to calculate the new orthographic size based on how the targets are moving and the shape of the target bounding box. Once we have the new orthographic size, we use a Lerp to more smoothly zoom the camera, while using Clamp to ensure we maintain a minimum orthographic size.

I've tested this with one to four simultaneous targets, and it seems to work quite well. With one target it simple behaves like a camera that's locked to the target and follows it around the world.

Hopefully this simple explanation is enough to get your started with the camera. If you have any further questions please feel free to message me on Twitter @RyanNielson or comment on this post's page.

Using Tags in Git

Over the last few days I've released a couple of Laravel 4 packages: Meta and Shareable. I use Git tags to manage the release versions, which integrate well with GitHub and Packagist(the main Composer repository). Using tags on GitHub automatically creates releases of your code that can be viewed or downloaded, and Packagist uses tags to determine available versions of your packages. Luckily, using tags is as easy as running the following commands in your terminal:

git tag -a 1.0.0 -m "Version 1.0.0"
git push --tags

The first line above create a tag called "1.0.0" and associates the message "Version 1.0.0" with it. The next line pushes your tags to your remote repository.

That's all there is to it. Tags provide a great mechanism for marking code releases, and provide nice reference points to older releases.

Handling Checkbox Input in Laravel

I've recently started working at a new start-up, and most of my work is now in PHP using Laravel. I was creating a basic CRUD interface, which included editing a user model that had boolean field called approved. Naturally this was a good use-case for using checkboxes. This worked fine until I tried to uncheck the approved checkbox and save. I noticed that the approved field on my model was still true while other changes were saving just fine.

The HTML specification states that unchecked checkboxes are not successful, so they are not submitted to the web server. I was using Input::all() to get user input, so this was not returning my change to the approved field as its checkbox was unchecked. A quick fix was to use Input::get('approved', false) to manually get the value, which returns false if approved isn't in the input. This was ugly and too manual for my tastes.

I often use Ruby on Rails, and I noticed it doesn't have this problem when editing models. It turns out that the Ruby on Rails check_box helper does a smart trick to avoid this issue, so I figured I'd copy their method. This is what I put in my form to get the desired affect:


{{ Form::hidden('approved', false) }}
{{ Form::checkbox('approved', true) }}

This inserts a hidden field in my form with the same name, but opposite value of the approved checkbox. Since the HTML specification says that inputs are sent to the server in the same order as they are in the form, this works flawlessly. What happens is if the checkbox is unchecked, the value approved gets submitted as false using the hidden field. But if it is checked, the checkbox's value gets submitted instead because it comes after the hidden field.

Just a quick little trick that should be useful to some of you out there. It'd be great to see Laravel's Form::checkbox() function handle this automatically though.