Been experimenting with this for a while, this is new animations with a new rig, not perfect but works wayyy better than it was before. I’ve also been experimenting with the Animation Rigging Package

seen from China
seen from Malaysia
seen from Finland

seen from United States
seen from Sweden

seen from Malaysia
seen from China
seen from Finland
seen from Germany
seen from TĂĽrkiye
seen from United States
seen from Japan
seen from China

seen from United States
seen from China
seen from China

seen from Malaysia
seen from TĂĽrkiye

seen from Germany

seen from France
Been experimenting with this for a while, this is new animations with a new rig, not perfect but works wayyy better than it was before. I’ve also been experimenting with the Animation Rigging Package

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
Radio Silence, and for what reason
A small little update from my side and most importantly the reason why I failed to post updates on my blog. A couple of months ago I figured I needed to swap out my PlayerController from being CharacterController based to Rigidbody based. It took quite some time rewriting the different player mechanics and unfortunately without the wanted results. The initial reason was simply because I thought the game needed more physics interaction and that only a Rigidbody based PlayerController would make this possible.
Unfortunately I was terribly wrong and I stumbled from one bug into the other (Camera following physics objects, FixedUpdate vs Update and the amount of control the player has over a Rigidbody PlayerController). Going back to a CharacterController was a really hard decision, since I felt all the work was for nothing. This however was not true. I eventually managed to fix issuesÂ
I previously had with the CharacterController component like sliding from edges and am currently reworking the pushing mechanics into a Push and Pull mechanic (This is almost done, but takes a lot of time because of the complexity and animations). None the less, I will try to maintain this blog alot more and I’m aiming for a playable first demo level around june/july.
Small preview of the Pull mechanics
Character controller interaction with rigidbodies
For the sake of puzzles I need the ability to push Rigidbodies, the problem is charactercontrollers don’t interact with physics from the get go. Some scripting for the charactercontroller makes it able to add a pushforce to rigidbodies and this is practically the current setup. This however causes some limitations like, which are fixed by using constraints. This doesn’t cause any issues on a flat surface, but when the player wants to push an object off a cliff it won’t be able to rotate in any direction.
The constraints on the rigidbody.
Working on character controller...
Character Controller
Using RayCasting
RayCastController.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (BoxCollider2D))] public class RayCastController : MonoBehaviour { public LayerMask collisionMask; public BoxCollider2D collider ; public RaycastOrigins raycastorigins; public int horizontalRaycount = 4; public int verticalRaycount = 4; //Setting the number of horizontal and vertical rays [HideInInspector] public float horizontalRayspacing ; [HideInInspector] public float verticalRayspacing; //Spacing between each horizonatal and vertical ray public const float skinWidth = .015f; //CameraController class has a start method, and this method will override it ...Awake method is called before Start() //Edit: Renaming this 'Start' method to 'Awake' method. public virtual void Awake(){ collider = GetComponent(); //Component required is the BoxCollider of the Player object } public virtual void Start(){ CalculateRaySpacing(); } public void UpdateRaycastOrigins(){ Bounds bounds = collider.bounds ; //Bounds class gives you the bounds of a particular object bounds.Expand(skinWidth * -2); //Replace new bounds of the RayCast origins raycastorigins.bottomleft = new Vector2(bounds.min.x,bounds.min.y); //Set the Origins raycastorigins.bottomright = new Vector2(bounds.max.x,bounds.min.y); raycastorigins.topleft = new Vector2(bounds.min.x,bounds.max.y); raycastorigins.topright = new Vector2(bounds.max.x,bounds.max.y); } public void CalculateRaySpacing(){ Bounds bounds = collider.bounds; bounds.Expand(skinWidth * -2); //Mathf.Clamp is used to fix a Range to the raycount horizontalRaycount = Mathf.Clamp(horizontalRaycount,2,int.MaxValue); verticalRaycount = Mathf.Clamp(verticalRaycount,2,int.MaxValue); horizontalRayspacing = bounds.size.y / (horizontalRaycount - 1 ); verticalRayspacing = bounds.size.x / (verticalRaycount - 1 ); } public struct RaycastOrigins{ //Structure that defines the corner points of the RaycastOrigins public Vector2 topleft, topright; public Vector2 bottomleft, bottomright; } }
Player.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof(PlayerController))] public class Player : MonoBehaviour { PlayerController controller; public float maxjumpHeight = 4f; public float minjumpHeight = 1f; public float timeToApexHeight = .4f; float moveSpeed = 6f; float accelerationAirBorne = .2f; float accelerationGrounded = 0.1f; float velocityReference; //Bunch of Vector2's for different wall jumps public Vector2 wallJumpClimb; public Vector2 wallJumpOff; public Vector2 wallJumpLeap; public float wallStickTime = .25f; public float timeToUnstick; //used for Sliding along the walls public float wallSlideSpeedMax = 3f; Vector3 velocity; float gravity; float maxjumpVelocity; float minjumpVelocity; void Start(){ controller = GetComponent(); gravity = -(2 * maxjumpHeight)/Mathf.Pow(timeToApexHeight,2); maxjumpVelocity = Mathf.Abs (gravity) * timeToApexHeight; minjumpVelocity = Mathf.Sqrt(2 * Mathf.Abs(gravity) * minjumpHeight); print ("Gravity"+ gravity + "\t JumpVelocity"+ maxjumpVelocity +"\tMin Jump velocity" + minjumpVelocity); } void Update(){ Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); // -1 if wall is to the left, 1 if the wall is to the right int wallDirX = (controller.collisions.left)? -1: 1; float targetVelocity = input.x * moveSpeed; //Smoothing the sideways movement of the player //velocity.x = targetVelocity * Time.deltaTime; //I think SmoothDamp will effectively give you the same result as multiplying by Time.DeltaTime velocity.x = Mathf.SmoothDamp(targetVelocity,velocity.x,ref velocityReference, (controller.collisions.below)? accelerationGrounded: accelerationAirBorne); bool wallSliding = false; //Used for sliding along the wall if((controller.collisions.left || controller.collisions.right) && !controller.collisions.below && velocity.y < 0 ){ wallSliding = true; if(velocity.y < -wallSlideSpeedMax){ velocity.y = -wallSlideSpeedMax; } if(input.x != wallDirX && input.x !=0){ velocityReference = 0; velocity.x = 0; if(timeToUnstick >= 0 ){ timeToUnstick -= Time.deltaTime; } else{ timeToUnstick = wallStickTime; } } else{ timeToUnstick = wallStickTime; } } if(Input.GetKeyDown(KeyCode.Space)){ //Wall sliding stuff and jump stuff along the walls if(wallSliding){ if(wallDirX == input.x){ //Case 1 where we want to jump on the same wall velocity.x = -wallDirX * wallJumpClimb.x; velocity.y = wallJumpClimb.y; } else if(input.x == 0){ //Case 2 where we want to jump off the walls to the ground velocity.x = -wallDirX * wallJumpOff.x; velocity.y = wallJumpOff.y; } else { //Case 3 where we want to jump to the adjacent wall velocity.x = -wallDirX * wallJumpLeap.x; velocity.y = wallJumpLeap.y; } } if(controller.collisions.below){ velocity.y = maxjumpVelocity; } } //Variable jump.. The y velocity is always greater than the minjumpVelocity if(Input.GetKeyUp(KeyCode.Space)){ if(velocity.y > minjumpVelocity){ velocity.y = minjumpVelocity; } } velocity.y += gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime, input); //Edit: The below lines were moved from Line 95 to here...The reason being the moving platform changes the collisions.above and below if(controller.collisions.above || controller.collisions.below){ velocity.y = 0; } } }
PlayerController.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof(BoxCollider2D))] //Setting the bounding area for our script public class PlayerController : RayCastController { public float maxClimbAngle = 80f; public float maxDescendAngle = 75f; public CollisionInfo collisions; Vector2 playerInput; //Start method overriding the start method in the RayCastController class public override void Start () { base.Start (); collisions.faceDirection = 1; } //Overriding the method in the PlatformController class public void Move(Vector3 velocity, bool standingOnPlatform = false){ Move(velocity, Vector2.zero, standingOnPlatform); } //Edit: Move() takes in a third parameter Vector2 input... //Used for controlling the input since PlayerController class has no control over the input public void Move(Vector3 velocity,Vector2 input, bool standingOnPlatform = false){ UpdateRaycastOrigins(); collisions.Reset (); playerInput = input; if(velocity.x != 0){ collisions.faceDirection = (int)Mathf.Sign (velocity.x); } HorizontalCollisions(ref velocity); if(velocity.y < 0){ DescendSlope(ref velocity); } if(velocity.y != 0){ VerticalCollisions(ref velocity); } if(standingOnPlatform ){ collisions.below = true; } transform.Translate (velocity); } /// /// Checks for vertical collisions. /// /// Velocity. /// In other words, when the RayCast hits the box collider, this functions gives the code to handle all the player behaviour void VerticalCollisions(ref Vector3 velocity){ //Direction is -1 when going down, 1 when going up float directionY = Mathf.Sign(velocity.y); float rayLength = Mathf.Abs(velocity.y) + skinWidth; for (int i =0; i< verticalRaycount; i++) { Vector2 rayOrigin = (directionY == -1 )? raycastorigins.bottomleft: raycastorigins.topleft; rayOrigin += Vector2.right * (verticalRayspacing * i + velocity.x); RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.up * directionY, rayLength, collisionMask ); //Just for debugging process..This statement can be omitted! Debug.DrawRay(rayOrigin , Vector2.up * directionY * rayLength, Color.red); if(hit) { //Script for allowing the players to jump through certain platforms if(hit.collider.tag == "ThroughPlatform"){ if(directionY == 1 || hit.distance == 0){ continue; } if(collisions.fallingThroughPlatform){ //The down Button pressed is not instantaneous if we want to fall through platform continue; } if(playerInput.y == -1){ //If the player wants to fall through the platform when downButtonPressed collisions.fallingThroughPlatform = true; Invoke("ResetFallingThroughPlatform",.5f); continue; } } velocity.y = (hit.distance - skinWidth) * directionY; rayLength = hit.distance; if(collisions.climbingSlope) velocity.x = velocity.y / (Mathf.Tan (Mathf.Deg2Rad * collisions.slopeNew) * Mathf.Sign (velocity.x)); collisions.above = directionY == 1; collisions.below = directionY == -1; } } if(collisions.climbingSlope){ float directionX = Mathf.Sign (velocity.x); rayLength = Mathf.Abs(velocity.x) + skinWidth; Vector2 rayOrigin = (directionX == -1)? raycastorigins.bottomleft: raycastorigins.bottomright + Vector2.up * velocity.y; RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.right * directionX, rayLength, collisionMask); if(hit){ float angle = Vector2.Angle(hit.normal, Vector2.up); if(angle != collisions.slopeNew){ velocity.x = (hit.distance - skinWidth) * directionX; collisions.slopeNew = angle; } } } } /// /// Checks for the horizontal collisions /// /// Velocity. /// In other words, when the RayCast hits the box collider, this functions gives the code to handle all the player behaviour void HorizontalCollisions(ref Vector3 velocity){ float directionX = collisions.faceDirection; float rayLength = Mathf.Abs(velocity.x) + skinWidth; //For wall climbing cases where the player is touching the wall but unable to slide... if(Mathf.Abs(velocity.x) < skinWidth){ rayLength = 2 * skinWidth; } for (int i =0; i< horizontalRaycount; i++) { Vector2 rayOrigin = (directionX == -1 )? raycastorigins.bottomleft: raycastorigins.bottomright; rayOrigin += Vector2.up * (horizontalRayspacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.right * directionX, rayLength, collisionMask ); Debug.DrawRay(rayOrigin , Vector2.right * directionX * rayLength, Color.red); if(hit) { if(hit.distance == 0){ continue; } //Finding the angle of the slope float slopeAngle = Vector2.Angle(hit.normal,Vector2.up); if(i == 0 && slopeAngle <= maxClimbAngle){ ClimbSlope(ref velocity, slopeAngle); } if(!collisions.climbingSlope || slopeAngle > maxClimbAngle){ velocity.x = (hit.distance - skinWidth) * directionX; rayLength = hit.distance; if(collisions.climbingSlope) velocity.y = Mathf.Tan(collisions.slopeNew * Mathf.Deg2Rad) * Mathf.Abs (velocity.x); collisions.left = directionX == -1; collisions.right = directionX == 1; } } } } //Method to climb slopes void ClimbSlope(ref Vector3 velocity, float slopeAngle){ float moveDistance = Mathf.Abs (velocity.x); float climbVelocityY = Mathf.Sin (slopeAngle * Mathf.Deg2Rad) * moveDistance; if(velocity.y
PlatformController.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; public class PlatformController : RayCastController { public LayerMask passengerMask; Vector3[] globalWayPointPos; public float speed; int fromWaypointIndex; float percentBetween; public bool cyclic; public float waitTime; float nextMoveTime; public Vector3[] localWayPoints; [Range(0,3)] public float easeAmount; List passengermovement; Dictionary passengerDictionary = new Dictionary(); public override void Start(){ base.Start(); globalWayPointPos = new Vector3[localWayPoints.Length]; for(int i = 0 ; i< localWayPoints.Length; i++ ){ globalWayPointPos[i] = localWayPoints[i] + transform.position; } } /// /// Smoothing the platform so that we provide some acceleration and deceleration during its travel /// This is done by the equation /// y = (x^a)/((x^a) + (1-x)^a)) /// For x = 1...There is no smoothing (The line is y = 1) /// Gives best results for x < 3 /// float PlatformSmoothing(float x){ float a = easeAmount + 1; return (Mathf.Pow (x,a)/(Mathf.Pow (x,a) + Mathf.Pow (1-x,a))); } public void Update(){ UpdateRaycastOrigins(); Vector3 velocity = CalculatePlatformMovement(); CalculatePassengerMovement(velocity); MovePassengers(true); transform.Translate(velocity); MovePassengers(false); } Vector3 CalculatePlatformMovement(){ if(Time.time <= nextMoveTime){ return Vector3.zero; } fromWaypointIndex %= globalWayPointPos.Length; int toWaypointIndex = (fromWaypointIndex + 1) % globalWayPointPos.Length ; //The next waypoint is simply the previous waypoint + 1 float distanceBetweenWaypoints = Vector3.Distance(globalWayPointPos[fromWaypointIndex],globalWayPointPos[toWaypointIndex]); percentBetween += Time.deltaTime * speed/ distanceBetweenWaypoints; float easeBetween = Mathf.Clamp01(percentBetween); Vector3 newPos = Vector3.Lerp(globalWayPointPos[fromWaypointIndex], globalWayPointPos[toWaypointIndex], PlatformSmoothing(easeBetween)); if(percentBetween >= 1){ percentBetween = 0; fromWaypointIndex ++; if(!cyclic){ if(fromWaypointIndex >= globalWayPointPos.Length- 1){ fromWaypointIndex = 0; System.Array.Reverse(globalWayPointPos); } } nextMoveTime = Time.time + waitTime; } return newPos - transform.position; } public void MovePassengers(bool beforeMovePlatform){ foreach(PassengerMovement passenger in passengermovement){ if(!passengerDictionary.ContainsKey(passenger.transform)){ passengerDictionary.Add(passenger.transform, passenger.transform.GetComponent()); } if(passenger.beforeMovePlatform == beforeMovePlatform){ //GetComponent calls is not good as far as optimization is concerned!!!! passenger.transform.GetComponent().Move(passenger.velocity, passenger.standingOnPlatform); } } } public void CalculatePassengerMovement(Vector3 velocity){ HashSet MovedPassengers = new HashSet(); passengermovement = new List(); float directionX = Mathf.Sign (velocity.x); float directionY = Mathf.Sign (velocity.y); ///Movement of the platform vertically if(velocity.y != 0){ float rayLength = Mathf.Abs(velocity.y) + skinWidth; for (int i =0; i< verticalRaycount; i++){ Vector2 rayOrigin = (directionY == -1 )? raycastorigins.bottomleft: raycastorigins.topleft; rayOrigin += Vector2.right * (verticalRayspacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.up * directionY, rayLength, passengerMask ); if(hit && hit.distance != 0) { if(!MovedPassengers.Contains(hit.transform)){ MovedPassengers.Add(hit.transform); float pushX = (directionY == 1)? velocity.x:0; float pushY = velocity.y - (hit.distance - skinWidth) * directionY; passengermovement.Add(new PassengerMovement(hit.transform, new Vector3(pushX,pushY), directionY == 1,true)); } } } } ///Movement of the platform horizontally if(velocity.x !=0){ float rayLength = Mathf.Abs(velocity.x) + skinWidth; for (int i =0; i< horizontalRaycount ; i++){ Vector2 rayOrigin = (directionX == -1 )? raycastorigins.bottomleft: raycastorigins.bottomright; rayOrigin += Vector2.up * (verticalRayspacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.up * directionX, rayLength, passengerMask ); if(hit && hit.distance != 0){ if(!MovedPassengers.Contains(hit.transform)){ MovedPassengers.Add(hit.transform); float pushX = velocity.x - (hit.distance - skinWidth) * directionX; float pushY = -skinWidth; //-skinWidth downward force so that the player is able to jump when squashed by the platform passengermovement.Add(new PassengerMovement(hit.transform, new Vector3(pushX,pushY), false,true)); } } } } //If a passenger is on top of horizontally or downward moving platform if(directionY == -1 || (velocity.y == 0 && velocity.x != 0) ){ float rayLength = skinWidth * 2f; for (int i =0; i< verticalRaycount; i++){ Vector2 rayOrigin = raycastorigins.topleft + Vector2.right * (verticalRayspacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.up, rayLength, passengerMask ); if(hit && hit.distance != 0){ if(!MovedPassengers.Contains(hit.transform)){ MovedPassengers.Add(hit.transform); float pushX = velocity.x; float pushY = velocity.y; passengermovement.Add(new PassengerMovement(hit.transform, new Vector3(pushX,pushY), true, false)); } } } } } public struct PassengerMovement{ public Transform transform; public Vector3 velocity; public bool standingOnPlatform; public bool beforeMovePlatform; public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _beforeMovePlatform){ transform = _transform; velocity = _velocity; standingOnPlatform = _standingOnPlatform; beforeMovePlatform = _beforeMovePlatform; } } //The local way points for the Platform Controller can be shown in the inspector.. For this, the OnDrawGizmos() function is used public void OnDrawGizmos(){ if(localWayPoints != null){ Gizmos.color = Color.red; float size = .3f; for (int i =0 ; i < localWayPoints.Length; i ++ ){ Vector3 globalWayPointPosition = (Application.isPlaying)? globalWayPointPos[i] : (localWayPoints[i] + transform.position); Gizmos.DrawLine(globalWayPointPosition - Vector3.up * size, globalWayPointPosition + Vector3.up * size); Gizmos.DrawLine(globalWayPointPosition - Vector3.left * size, globalWayPointPosition + Vector3.left * size); } } } }
CameraController.cs
using UnityEngine; using System.Collections; /// /// Camera Controller Class for controlling the movement of the MainCamera /// There will be a Rectangular Area focusing on the Player. The Player can move left, right by touching the boundaries of the focus Area. /// The camera will follow the focus Area depending upon the direction in which the Player moves /// This is a popular convention to make the Camera follow the player, and it is used because it requires less camera movement to track the player. /// public class CameraController : MonoBehaviour { public Vector2 focusAreaSize; //For an area around the player that focuses on the player public PlayerController controller; FocusArea focusArea; public float verticalOffset; void Start(){ focusArea= new FocusArea(controller.collider.bounds, focusAreaSize); } void LateUpdate(){ focusArea.UpdateBounds(controller.collider.bounds); Vector3 focusOffset = focusArea.center + Vector2.up * verticalOffset; transform.position = (Vector3)focusOffset + Vector3.forward * -10; } //Just used for Debugging purposes void OnDrawGizmos(){ Gizmos.color = new Color(1,0,0,.5f); Gizmos.DrawCube(focusArea.center, focusAreaSize); } struct FocusArea{ public Vector2 center; float left, right; float top, bottom; public Vector2 velocity; public FocusArea(Bounds targetBounds, Vector2 size){ left = targetBounds.center.x - size.x/2; right = targetBounds.center.x + size.x/2; bottom = targetBounds.min.y; top = targetBounds.min.y + size.y; center = new Vector2((left+ right)/2,(top + bottom)/2); velocity = Vector2.zero; } public void UpdateBounds(Bounds targetBounds){ //In the horizontal direction 'x' float shiftX = 0; if(targetBounds.min.x < left){ shiftX = targetBounds.min.x - left; } else if(targetBounds.max.x > right){ shiftX = targetBounds.max.x - right; } left += shiftX; right += shiftX; //In the vertical direction 'y' float shiftY = 0; if(targetBounds.min.y < bottom){ shiftY = targetBounds.min.y - bottom; } else if(targetBounds.max.y > top){ shiftY = targetBounds.max.y - top; } bottom += shiftY; top += shiftY; center = new Vector2((left+ right)/2,(top + bottom)/2); velocity = new Vector2(shiftX,shiftY); } } }

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
Anton, I noticed that the movement of your main character is very smooth and solid. The stopping in place after running is almost instantenous, so I asume you're not using 2D physics for it? Or am I wrong? Would you be so kind to share the method you used to solve the character controller?
It took a lot of tries and tweaks. I actually ended up using 2D physics for my character, but I’m modifying velocity directly, instead of applying force. This line is in my FixedUpdate():> if(Mathf.Abs(speed)>.5f && !frontBlocked)> myRigidbody.velocity = new Vector2(speed, myRigidbody.velocity.y);To get the smoothness I Lerp the speed (h is the controller’s X input). These are from Update():> if(Mathf.Abs (h)>0 || !frontBlocked)>  speed =  Mathf.Lerp(speed, h, Time.deltaTime*16);Stopping the character, bit more faster speed drop:> if(h==0 && Mathf.Abs(speed)>0 || frontBlocked)>  speed = Mathf.Lerp(speed, 0, Time.deltaTime*32);And to add a little weight to character’s feel, I have a delay if player decides to do a quick turn:Combined with animations, it feels pretty nice, even when airborne: I can just tap jump and left and Rigidbody will have some horizontal velocity and not just go straight up. Then, mid-jump I can control my character too, with same “delay if doing quick turns” applied.In future I want to try adding fast running, dash and variable jump height too.