[WIP] Platformer Pro

LATEST VIDEO

This thread will track progress via video updates.

WIP 1 - A look at some of the improved movement for ladders and passthrough platforms.

2 Likes

Mobile users will be pleased to note … v2 has no allocation (at the minor usability sacrifice of using layers instead of tags).

I’ll try and ensure this stays true for all the capabilities, although I expect along the way there may be a few where this isn’t the case.

(The on mouse stuff is the Unity editor)

1489991--83106--$Screen shot 2014-01-20 at 9.24.14 AM.png

Platforms and triggers:

I’m confused about the pricing - If I were to buy the 2d platform control asset now, will I still have to pay an upgrade cost when the Platform PRO is released? Or am I incentivized to purchase it now and get this future upgrade for free?

Updates:

1 Like

Hi Johnny,

I’m really thinking about buying your package but I have a few questions :

  • I’m using 2D Toolkit package to handle sprites and I’m wondering if you have it and already tried to use your controller with ? I guess there’s no reason they won’t work well together but it’s always good to ask if you know any incompatibility.

  • Is your controller able to handle a “square collider” (no “capsule” one) on righ-angled edge ? My game is a “retro/pixel” platformer and this “sharp” and precise collision feeling is very important.

  • If yes to the previous question, is the “square collider” able to walk on slope ? Without rotation ?

Thanks !

PS : I remember you comment my article on Gamasutra about my “hobbyist coder” approach for a 2D platformer controller. Your work looks definitely stronger and more optimized than mine so I would probably save a lot of time using it :slight_smile:

Hi Aldwin, loved your article. As to your questions:

  1. The existing version includes a 2D sample built with 2DTK. Play it here: http://jnamobile.com/samples/2DPlatformController/AlienSample.html

  2. It can be set up in a square shape and has pixel perfect falling off of the edges. The new version is even better as it corrects some of the minor issues in the previous version (e.g. snapping to the top of a passthrough platform). It also makes things ten times easier to set up.

  3. Collider can walk on slopes in the existing version but no rotation requires a little extra code. In the new version its handled natively… see the video above from 45 seconds onwards.

Cheers,

John A

Hi Johnny, thanks a lot for your quick answers !

But I have some few new ones, actually :

  • Is your jump system using some kind of “input call tolerance” ? I mean is a jump call registered when not grounded but very close to be ?

  • Do you implemented a tweakable “reactivity factor” when changing direction to avoid the “walking on ice” feeling ?

  • Is it possible (and easy) to just use your “collision system” and combine it with a custom “move/jump system” ? This his is the most important for me (if yes, no matter the 2 first questions actually!)

Sorry fo all these questions ^^

Cheers,

Yoann

Again it depends if you mean now or in Platformer PRO.

  1. Yes in both but better in PRO.

  2. I’m not quite sure which feel you mean. There are different movement systems in 2DPC. In 2DPC the PHYSICS_LIKE system has friction and acceleration which are configurable. The DIGITAL system applies velocity immediately. The DIGITAL_WITH_SLIDE system slides to a stop like a physics system but changes direction immediately when the user presses a key. For pro see 3.

  3. In Platformer PRO the movement system is completely pluggable. If you follow a specific pattern your movements will appear in the custom editor as first-class citizens exactly like the mvoements that come with the kit. You can also short-cut the process and implement only the movement part in which case you can drop the movement on the character and have it work, but it wont be quite as pretty :slight_smile:

Basically the main controller asks relevant movements if they want control. If they do they are given control until they decide to give it back. They can also optionally choose to apply base collisions (i.e. don’t go through walls) if they want (amongst other things).

So you could turn your character in to a tilt controlled ball until they go down a certain hole and then turn them back in to a normal person if you wanted.

Here’s the base movement class for reference (subject to change although probably wont):

namespace PlatformerPro
{
	/// <summary>
	/// This is the basic behaviour from which all movement behaviours are derived. To create your 
	/// own movement extend this class.
	/// </summary>
	public abstract class Movement : MonoBehaviour
	{
		#region members

		/// <summary>
		/// Cached reference to the character.
		/// </summary>
		protected Character character;

		#endregion

		#region properties

		/// <summary>
		/// Gets a value indicating whether this <see cref="PlatformerPro.Movement"/> expects
		/// gravity to be applied after its movement finishes.
		/// </summary>
		virtual public bool ShouldApplyGravity
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Gets a value indicating the current gravity, only used if this
		/// movement doesn't apply the default gravity.
		/// <seealso cref="ShouldApplyGravity()"/>
		/// </summary>
		virtual public float CurrentGravity
		{
			get
			{
				return 0.0f;
			}
		}

		
		/// <summary>
		/// Gets a value indicating whether this <see cref="PlatformerPro.Movement"/> expects the
		/// base collisions to be executed after its movement finishes.
		/// </summary>
		virtual public bool ShouldDoBaseCollisions
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this <see cref="PlatformerPro.Movement"/> expects the
		/// rotations to be calculated and applied by the character.
		/// </summary>
		virtual public bool ShouldDoRotations
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Gets the animation state that this movement wants to set.
		/// </summary>
		virtual public AnimationState AnimationState
		{
			get 
			{
				return AnimationState.IDLE;
			}
		}

		/// <summary>
		/// Returns the direction the character is facing. 0 for none, 1 for right, -1 for left.
		/// </summary>
		virtual public int FacingDirection
		{
			get 
			{
				return 0;
			}
		}

		#endregion

		#region movement info constants and properties
		
		/// <summary>
		/// Human readable name.
		/// </summary>
		private const string Name = "Movement";
		
		/// <summary>
		/// Human readable description.
		/// </summary>
		private const string Description = "The base movement class, you shound't be seeing this did you forget to override MovementInfo?";
		
		/// <summary>
		/// Static movement info used by the editor.
		/// </summary>
		public static MovementInfo Info
		{
			get
			{
				return new MovementInfo(Name, Description);
			}
		}
		
		#endregion

		#region public methods

		/// <summary>
		/// Initialise this movement and retyrn a reference to the ready to use movement.
		/// </summary>
		virtual public Movement Init(Character character)
		{
			this.character = character;
			return this;
		}

		
		/// <summary>
		/// Initialise the movement with the given movement data.
		/// </summary>
		/// <param name="character">Character.</param>
		/// <param name="movementData">Movement data.</param>
		virtual public Movement Init(Character character, MovementVariable[] movementData)
		{
			Debug.LogError("The Movement class shouldn't be initialised with movement data. Did you forget to override the Init method?");
			return this;
		}

		/// <summary>
		/// Moves the character.
		/// </summary>
		virtual public void DoMove()
		{

		}

		/// <summary>
		/// If this is true then the movement wants to maintain control of the character even
		/// if default transition conditions suggest it shouldn't.
		/// </summary>
		/// <returns><c>true</c> if control is wanted, <c>false</c> otherwise.</returns>
		virtual public bool WantsControl()
		{
			return false;
		}

		/// <summary>
		/// Determines whether this character is grounded. Only applied if ShouldDoBaseCollisions returned false this frame.
		/// Else the IsGrounded() method from the base collisions will be used.
		/// </summary>
		/// <returns><c>true</c> if this character is grounded; otherwise, <c>false</c>.</returns>
		virtual public bool IsGrounded()
		{
			return false;
		}
		#endregion
	}

}

Thats all you need for simple movement. If you want editor integration then instead extend a specific moment class like (Ground, Air, Wall, Ladder, Ledge, Damage, Water, etc). These add movement specific methods and proeprties as well as plug in to the editor. An example:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace PlatformerPro
{
	/// <summary>
	/// A wrapper class for handling the characters movement (or lack thereof) when the character is dying.
	/// </summary>
	public class DamageMovement : BaseMovement <DamageMovement>
	{

		#region movement info constants and properties
		
		/// <summary>
		/// Human readable name.
		/// </summary>
		private const string Name = "Damage Movement";
		
		/// <summary>
		/// Human readable description.
		/// </summary>
		private const string Description = "The base damage movement class, you shouldn't be seeing this did you forget to create a new MovementInfo?";

		/// <summary>
		/// Static movement info used by the editor.
		/// </summary>
		new public static MovementInfo Info
		{
			get
			{
				return new MovementInfo(Name, Description);
			}
		}

		#endregion

		/// <summary>
		/// Information about the damage sustained.
		/// </summary>
		protected DamageInfo damageInfo;

		/// <summary>
		/// Is this being used as a death movement.
		/// </summary>
		protected bool isDeath;

		/// <summary>
		/// For damage the default is to not apply gravity.
		/// </summary>
		override public bool ShouldApplyGravity
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// Start the damage movement.
		/// </summary>
		/// <param name="info">Info.</param>
		/// <param name="isDeath">Is the movement being used for death.</param>
		virtual public void Damage(DamageInfo info, bool isDeath)
		{

		}
	}

}

Wow this is looking really superb! I’m excited for the release!

I’m interested in how you got mecanim animations to play without using transitions. For 2D games I find transitions to be a hassle more often than not, it would be great if you could share something about that.

myAnimator.Play(args.State.AsString());

Where args.State.AsString() resolves to a state name in the mecanim animator. There were a few tricks though… making sure the animation isn’t skipped (because mecanim is stupid) requires something like this:

void Update()
{
	// Ensure we played the state for at least one frame, this is to work around for Mecanim issue where calling Play isn't always playing the animation
	if (myAnimator.GetCurrentAnimatorStateInfo(0).IsName(state.AsString()))
	{
		hasPlayed = true;
		// Now play the queued state
		if (queuedState != AnimationState.NONE)
		{
			myAnimator.Play(queuedState.AsString());
			state = queuedState;
			queuedState = AnimationState.NONE;
			hasPlayed = false;
		}
	}
}

AnimationState is my own enum of possible animations which maps to the state name in mecanim.

Cool thanks for the info! I kinda like mecanim but most of the time it seems to get in the way.

Sometimes doing something simple is frustratingly complex, on the other hand doing complex stuff with it is often quite simple :wink:

New video:

Latest Platformer PRO update, I really like this one:

When do you plan to release a new version?
I recently learned about GC.Collect in 1.9, because of which it is impossible to use it on mobile platforms, very upset.

1.9 is fine on mobile, the only core controller function called each frame that allocates is using RaycastAll which is only a few hundred bytes (depending on number of colliders). At that rate you might get a slight pause every 10 minutes. Of course you can always trigger a garbage collect at a suitable time like when the user dies or finished a level.

On ropes and ladders you get a bit more allocation but again its due to raycast all. If somehow this is causing you issue I have an alternate implementation of RaycastAll which doesn’t allocate which I can share if you submit a support request.

Note that GetComponent allocates in the editor not in the runtime.

Platformer PRO version has no fixed released date, I expect it in a few months.

Hey Johnny,

Didn’t see this implemented in 1.9 so I thought I’d ask for a feature request: I’m working on Castle Kong and one of the things i’m trying to mimic is rigid movement. For example, I don’t want the player to be able to reverse course after jumping. Whatever his initial jump velocity is stays the same through the course of the jump, rather than being affected by gravity. Should be fairly easy to implement, and this way I don’t have to overwrite your code and track changes on every update.

Thanks!

Hey Johnny,

Big fan of your work on this asset. I was able to get fantastic controls running in a matter of minutes. Really looking forward to Platformer Pro.

I’ve got two questions:

  1. I’ve done a few modifications to mine to add Xbox controller support (including analog movement). Is Platformer Pro going to support this out of the box? If not I’m sure I can integrate my changes to the new version without too much trouble.

  2. My character occasionally tunnels through collision geometry. I have the controller movement speed cranked up pretty high (think Super Meat Boy). This higher speed is almost certainly the cause. But I really like this control style and do not want to sacrifice speed for collision stability. Can you suggest anything I might do to prevent the character from tunneling through my collision geometry (collision is primarily BoxCollider2D and PolygonCollider2D triangles for slopes).