Random image of Textel, my bat fursona

COBERTOS

My last full song for a while

Mini BossMusicLmmsElectronic

My last full song for a while, if not ever. Making music just doesn’t have the drive it used to for me (though playing music on the other hand…).

I may end up posting some older WIP stuff but nothing new.

The art is from Reinforcement’s game Quackventure. I liked the monster art design (I thought it was really cute) so I ended up rendering it in Blender into a song cover.

Download is free as always on SoundCloud.

Read More »

Some more rendering stuff

Final 1920 by 1080 render of the thing

Continuing from my last rendering post, the spider mini boss in “Quackventure” shoots these little energy balls at the player. I added the energy ball after the render in Paint.NET to get this neat little scene.

It only took a couple hours of work and I got such a nice result for the time spent! Didn’t think it would be this easy given that I’ve never performed a render before. Thanks cycles!

Anyway, the picture above is the final render in 1920x1080, enjoy.

Read More »

Another 4am Blender night...

editor 1

editor 2

render of spider image

This is a product of another 4am night playing around working with Blender’s Cycles renderer for some art for a song.

My friend ended up giving me some models from his game “Quackventure” for the album art (the song is called Mini Boss, these are a Mini Boss from his game). I retextured everything, made a scene for the little spiders, and had fun posing the little eyes and sculpting the rocks and the webs. The rendering above is just a quick progress picture as the final render is currently rendering in the background.

I’ll post the final one in the morning when it’s done and the song sometime soon.

Read More »

Why Python is so exciting for me

Mp3 TaggingMp3Python

I’m in love with Python. Like madly in love. If I wasn’t pretty crazy about this one girl right now I’m sure that I’d probably start hanging out with Python more, take it out to dinner, get to know it better, you know how it is.

I remember when I first looked at Python and went “What in the hell, who would want to program in this?! No braces? Colons?? How do you read the code??” Eventually I grew to really love it and has become possibly my favorite to program in (on the same level as C#). I know some that will try to force their beliefs on others when it comes to languages and IDEs. I only want to share with you my excitement for this language. In terms of enjoyment though, I really do believe a favorite language is based on personal taste and people should respect that.

But why do I love it? I could probably go on and on about the exact features I like but that’d get a little repetitive. If I had to choose one thing I’d have to say the simplicity of the collections matched with their flexibility and power is my favorite feature. Also, the fact that there’s a library that pretty much does anything you want just one console command away doesn’t hurt either (with PyPI).

Python is also the first language I could just rattle out simple tools to serve my life with.

  • Need to backup my PC? Built a backup script before packing up to leave for college.
  • Need to run 20 files through some programs for school over the next 7 weeks? Wrote up a simple script to saves me hours of work in looking over the logs of the programs.
  • My latest project with Python (pictured below) has been to tag all my music files. I just wanted the album and album art for my songs. The program corrects all needed searching fields, searches Discogs and Google for album info, and then uses an external program to get an image to embed in the mp3 all in just two days of work. Two Days!

Sample program output (a search on Discogs)

#[3] Try discogs search, list first few
req = requests.get("http://www.discogs.com/search/?q=" + searchTextEnc)
if req.status_code == 200:
    results = []
    album = None
    try:
        soup = BeautifulSoup(req.text, "html.parser")
        resEl = soup.find(id="search_results")
        for divEl in resEl.select("div.card"):
            #Try to get the artist and album
            try:
                foundAlbum = divEl.find("h4").find("a").string.strip()
                foundArtist = divEl.find("h5").find("a").string.strip()
            except: #Possibly no artist or album for given "card"
                continue
                
            results.append((foundAlbum, foundArtist))
            if len(results) > 20:
                break
    except:
        results = None
    
    if results:
        #Display results for user to choose
        for i, (alb, art) in enumerate(results):
            printStr = "[" + str(i) + "] " + alb + " by " + art
            print(printStr.encode("ascii", "replace").decode("ascii"))

I tried a similar thing years ago with Perl for managing a large texture database. I was just not able to get the same speed or robustness from the program that I am akin to with my Python abilities.

While Python is my most recently learned language the speed at which I can code and the way the code fits with my mindset just works so well. While I do dread the ever nearing obsolescence date of any coding language that I use, I wonder if at some point in the future another language will be created that will top my enjoyment for Python.

Read More »

Just another old song

Music

Just another song that had been sitting around for a while. I finally got to finishing it up, balancing everything out, etc. Came out well but there are a few things that I’m a bit unhappy with (formulaic structure mostly).

Hope you like it! Download is free on SoundCloud.

Read More »

How to climb stairs as a Rigidbody (in Unity3D)

How to climb stairs as a Rigidbody

So you want to add some stairs to your game? Great! Adding stairs in level design adds more vertical variety to your levels. Or perhaps your game is based on stairs (Stair Climbing Simulator 2018?).

In this tutorial I'll show you how to implement stair climbing from scratch in Unity3D if you're using a Rigidbody as your player. Before we get to coding, you should consider your (much easier / less work) alternatives:

  1. Use a CharacterController. It already has some support for this, but can't have physics applied willy nilly, nor is it as customizable as a Rigidbody with multiple colliders.
  2. Use a CapsuleCollider with your Rigidbody. If you get the player going fast enough they can just ram right over the stairs.
  3. Use ramps or stairs with invisible ramps over the steps. They work just as well and even provide a smooth transition surface. Doesn't work for cases of dynamic/physics content or player generated content.

If you've looked into these and you still need stairs, let's dive in.

Scope and Requirements

This tutorial uses Unity3D and C# but the concepts should be the same (except for the Unity specific quirks I mention). The stair stepping style is based off of the behavior found in the Source Engine.

You should also have a working MonoBehaviour script for simple player controls that uses a Rigidbody component. It should be able to move at a minimum. If you don't, RigidbodyFPSWalker is a decent start

Configurable Options

The stair stepping can be tweaked in numerous ways. For now, we will define only two parameters explicitly that will be used later in the tutorial.

public float maxStepHeight = 0.4f;        // The maximum a player can set upwards in units when they hit a wall that's potentially a step
public float stepSearchOvershoot = 0.01f; // How much to overshoot into the direction a potential step in units when testing. High values prevent player from walking up tiny steps but may cause problems.

A quick look at ContactPoints

The physics engine in Unity3D, PhysX, generates collision events (OnCollisionEnter, OnCollisionStay, ...). These collision events return a Collision object with gobs of information.

A nifty little property on this object is the Collision[.contacts](http://docs.unity3d.com/ScriptReference/Collision-contacts.html) member which gives a list of all of the points of contact in a collision (well, usually all of them, sometimes it doesn't but only with complex collision MeshColliders hitting each other).

The ContactPoints come with a position, a normal, and collider information.

For visualization, here's a collision between my player (a green BoxCollider), a surface, and the resulting ContactPoints. The ContactPoints are visualized as blue lines.

A contact point

Getting the ContactPoints

We need all the ContactPoints every physics frame. To capture them we save all ContactPoints generated by both OnCollisionEnter and OnCollisionStay events.

List<ContactPoint> allCPs = new List<ContactPoint>();

void OnCollisionEnter(Collision col)
{
    allCPs.AddRange(col.contacts);
}
void OnCollisionStay(Collision col)
{
    allCPs.AddRange(col.contacts);
}

We need to loop through all the ContactPoints and then do the stair step. It is best to do this in FixedUpdate as we're working with physics data which is only generated every physics frame.

void FixedUpdate()
{
    //Do stair stepping
    allCPs.Clear(); //Deletes all ContactPoints collected from the last physics frame
}

Detecting ground and a stair

Now that we have all the ContactPoints for the player we need to look through them for some specifics

For the rest of the tutorial this will be the example situation we'll be looking at

Player and it's collider approaching a step

Detecting the ground

Detecting the ground must be done before looking for stair steps. We can say that ground is any ContactPoint with a Y (up) normal component of greater than 0.0001 meaning that it has some sort of upward direction to it.

/// Finds the MOST grounded (flattest y component) ContactPoint
/// \param allCPs List to search
/// \param groundCP The contact point with the ground
/// \return If grounded
bool FindGround(out ContactPoint groundCP, List<ContactPoint> allCPs)
{
    groundCP = default(ContactPoint);
    bool found = false;
    foreach(ContactPoint cp in allCPs)
    {   
        //Pointing with some up direction
        if(cp.normal.y > 0.0001f && (found == false || cp.normal.y > groundCP.normal.y))
        {
            groundCP = cp;
            found = true;
        }
    }
    
    return found;
}

From this function we are able to determine if the player is grounded as well as at what Y value the player is grounded.

//In FixedUpdate
ContactPoint groundCP = default(ContactPoint);
bool grounded = FindGround(out groundCP, allCPs);

Detecting a stair

Detecting a stair is a much more intensive process that requires a lot of math. FindStep does very similar things to FindGround above.

First, we don't care about any stairs if the player is not moving.

Second, the actual checks are done in ResolveStepUp but the process is the same, check every ContactPoint.

/// Find the first step up point if we hit a step
/// \param allCPs List to search
/// \param stepUpOffset A Vector3 of the offset of the player to step up the step
/// \return If we found a step
bool FindStep(out Vector3 stepUpOffset, List<ContactPoint> allCPs, ContactPoint groundCP)
{
    stepUpOffset = default(Vector3);
    
    //No chance to step if the player is not moving
    Vector2 velocityXZ = new Vector2(currVelocity.x, currVelocity.z);
    if(velocityXZ.sqrMagnitude < 0.0001f)
        return false;
    
    foreach(ContactPoint cp in allCPs)
    {
        bool test = ResolveStepUp(out stepUpOffset, cp, groundCP);
        if(test)
            return test;
    }
    return false;
}

Now we start building ResolveStepUp

/// Takes a contact point that looks as though it's the side face of a step and sees if we can climb it
/// \param stepTestCP ContactPoint to check.
/// \param groundCP ContactPoint on the ground.
/// \param stepUpOffset The offset from the stepTestCP.point to the stepUpPoint (to add to the player's position so they're now on the step)
/// \return If the passed ContactPoint was a step
bool ResolveStepUp(out Vector3 stepUpOffset, ContactPoint stepTestCP, ContactPoint groundCP)
{
    stepUpOffset = default(Vector3);
    Collider stepCol = stepTestCP.otherCollider; //You'll need the collider of the potential step for this
    //Determine if stepTestCP is a stair...
    return true; //If stepTestCP is a stair
}

For ContactPoints to be considered a stair, they have to remotely look like a stair. When the player hits the stairs, it's like hitting a really short wall and it will generateContactPoints with really low Y (up) directional components for their normals. So, for each ContactPoint, we only used the ones with low Y normal values

//( 1 ) Check if the contact point normal matches that of a stair (y close to 0)
if(Mathf.Abs(stepTestCP.normal.y) >= 0.01f)
{
    return false;
}

Considering this check says nothing about the height at which this ContactPoint occured, it should filter out all the ones that are too high for the player to step up to

//( 2 ) Make sure the contact point is low enough to be a stair
if( !(stepTestCP.point.y - groundCP.point.y < maxStepHeight) )
{
    return false;
}

We've now gathered quite a bit of data.

A player and collider contacting the stair at a given point with certain pieces of data

Determining where to step up

We've detected a stair, this is cool, but we don't know how to get up it.

//( 3 ) Check to see if there's actually a place to step in front of us
//Fires one Raycast
RaycastHit hitInfo;
float stepHeight = groundCP.point.y + maxStepHeight + 0.0001f;
Vector3 stepTestInvDir = new Vector3(-stepTestCP.normal.x, 0, -stepTestCP.normal.z).normalized;
Vector3 origin = new Vector3(stepTestCP.point.x, stepHeight, stepTestCP.point.z) + (stepTestInvDir * stepSearchOvershoot);
Vector3 direction = Vector3.down;
if( !(stepCol.Raycast(new Ray(origin, direction), out hitInfo, maxStepHeight)) )
{
    return false;
}

Raycasts for determining whether we can step up or not

//We have enough info to calculate the points
Vector3 stepUpPoint = new Vector3(stepTestCP.point.x, hitInfo.point.y+0.0001f, stepTestCP.point.z) + (stepTestInvDir * stepSearchOvershoot);
Vector3 stepUpPointOffset = stepUpPoint - new Vector3(stepTestCP.point.x, groundCP.point.y, stepTestCP.point.z);
return true; //We're going to step up!

How to step up

We hit something, figured out it was a stair, and even got the position to where we're going to step up. We did this all in the beginning of FixedUpdate and we will now check at the end whether to step up or not.

NOTE: You'll need the last physics frame velocity in order to make the step feel smooth as the player is stopped by the time we read the ContactPoint where they hit the wall.

//In FixedUpdate()
Vector3 stepUpOffset = default(Vector3);
bool stepUp = false;
if(grounded)
    stepUp = FindStep(out stepUpOffset, allCPs, groundCP, velocity);

if(stepUp)
{
    //Take the RigidBody and apply the stepUpOffset to its position
    this.GetComponent<Rigidbody>().position += stepUpOffset;
    //When it hit the stair, it stopped our player, so reapply their last velocity
    this.GetComponent<Rigidbody>().velocity = lastVelocity; //You'll need to store this from the last physics frame...
}

Full code

Combining everything above, here's the full code put together. You will have to modify this to play nicely with your version ofRigidBodyFPSWalker or the like.

[RequireComponent (typeof (Rigidbody))]
//Also requires some sort of collider, made with an AABB in mind

/// The class that takes care of all the player related physics
/// Many configurable parameters with defaults set as the recommended values in BBR
public class StepClimber : MonoBehaviour {
    //Remember 1 unit is 1 meter (the physics engine was made with this ratio in mind)
    
    [Header("Steps")]
    public float maxStepHeight = 0.4f;              ///< The maximum a player can set upwards in units when they hit a wall that's potentially a step
    public float stepSearchOvershoot = 0.01f;       ///< How much to overshoot into the direction a potential step in units when testing. High values prevent player from walking up small steps but may cause problems.
    
    private List<ContactPoint> allCPs = new List<ContactPoint>();
    private Vector3 lastVelocity;

    void FixedUpdate()
    {
        Vector3 velocity = this.GetComponent<Rigidbody>().velocity;
        
        //Filter through the ContactPoints to see if we're grounded and to see if we can step up
        ContactPoint groundCP = default(ContactPoint);
        bool grounded = FindGround(out groundCP, allCPs);
        
        Vector3 stepUpOffset = default(Vector3);
        bool stepUp = false;
        if(grounded)
            stepUp = FindStep(out stepUpOffset, allCPs, groundCP, velocity);
        
        //Steps
        if(stepUp)
        {
            this.GetComponent<Rigidbody>().position += stepUpOffset;
            this.GetComponent<Rigidbody>().velocity = lastVelocity;
        }
        
        allCPs.Clear();
        lastVelocity = velocity;
    }
 
    void OnCollisionEnter(Collision col)
    {
        allCPs.AddRange(col.contacts);
    }
 
    void OnCollisionStay(Collision col)
    {
        allCPs.AddRange(col.contacts);
    }
    
    /// Finds the MOST grounded (flattest y component) ContactPoint
    /// \param allCPs List to search
    /// \param groundCP The contact point with the ground
    /// \return If grounded
    bool FindGround(out ContactPoint groundCP, List<ContactPoint> allCPs)
    {
        groundCP = default(ContactPoint);
        bool found = false;
        foreach(ContactPoint cp in allCPs)
        {   
            //Pointing with some up direction
            if(cp.normal.y > 0.0001f && (found == false || cp.normal.y > groundCP.normal.y))
            {
                groundCP = cp;
                found = true;
            }
        }
        
        return found;
    }
    
    /// Find the first step up point if we hit a step
    /// \param allCPs List to search
    /// \param stepUpOffset A Vector3 of the offset of the player to step up the step
    /// \return If we found a step
    bool FindStep(out Vector3 stepUpOffset, List<ContactPoint> allCPs, ContactPoint groundCP, Vector3 currVelocity)
    {
        stepUpOffset = default(Vector3);
        
        //No chance to step if the player is not moving
        Vector2 velocityXZ = new Vector2(currVelocity.x, currVelocity.z);
        if(velocityXZ.sqrMagnitude < 0.0001f)
            return false;
        
        foreach(ContactPoint cp in allCPs)
        {
            bool test = ResolveStepUp(out stepUpOffset, cp, groundCP);
            if(test)
                return test;
        }
        return false;
    }
    
    /// Takes a contact point that looks as though it's the side face of a step and sees if we can climb it
    /// \param stepTestCP ContactPoint to check.
    /// \param groundCP ContactPoint on the ground.
    /// \param stepUpOffset The offset from the stepTestCP.point to the stepUpPoint (to add to the player's position so they're now on the step)
    /// \return If the passed ContactPoint was a step
    bool ResolveStepUp(out Vector3 stepUpOffset, ContactPoint stepTestCP, ContactPoint groundCP)
    {
        stepUpOffset = default(Vector3);
        Collider stepCol = stepTestCP.otherCollider;
        
        //( 1 ) Check if the contact point normal matches that of a step (y close to 0)
        if(Mathf.Abs(stepTestCP.normal.y) >= 0.01f)
        {
            return false;
        }
        
        //( 2 ) Make sure the contact point is low enough to be a step
        if( !(stepTestCP.point.y - groundCP.point.y < maxStepHeight) )
        {
            return false;
        }
        
        //( 3 ) Check to see if there's actually a place to step in front of us
        //Fires one Raycast
        RaycastHit hitInfo;
        float stepHeight = groundCP.point.y + maxStepHeight + 0.0001f;
        Vector3 stepTestInvDir = new Vector3(-stepTestCP.normal.x, 0, -stepTestCP.normal.z).normalized;
        Vector3 origin = new Vector3(stepTestCP.point.x, stepHeight, stepTestCP.point.z) + (stepTestInvDir * stepSearchOvershoot);
        Vector3 direction = Vector3.down;
        if( !(stepCol.Raycast(new Ray(origin, direction), out hitInfo, maxStepHeight)) )
        {
            return false;
        }
        
        //We have enough info to calculate the points
        Vector3 stepUpPoint = new Vector3(stepTestCP.point.x, hitInfo.point.y+0.0001f, stepTestCP.point.z) + (stepTestInvDir * stepSearchOvershoot);
        Vector3 stepUpPointOffset = stepUpPoint - new Vector3(stepTestCP.point.x, groundCP.point.y, stepTestCP.point.z);
        
        //We passed all the checks! Calculate and return the point!
        stepUpOffset = stepUpPointOffset;
        return true;
    }
}

Other checks

Keep in mind the only real restriction that we placed on the player was that they can only step up things with a horizontal normal and things that are under a specific height. There are a few corner cases with this method including

  • Making sure that if the player creeps back down a step, it doesn't try to step them back up.
  • Player is too wide to step up into the given area.
  • When a collider is deleted in between physics frames (null checks needed on colliders in ContactPoints)

but these I'll leave these up to you to implement. They build off of the checks in ResolveStepUp

Read More »
Newer posts