Announcement

Collapse
No announcement yet.

[VIDEO] Transform a Skeletal Mesh Pawn To Moving Jumping Kactor Ball and Back Again

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [VIDEO] Transform a Skeletal Mesh Pawn To Moving Jumping Kactor Ball and Back Again

    Dear Community,

    This is something I've wanted to accomplish for awhile!

    In this video below and in my code I am giving you, I show you how to:

    1. use a single button press to turn a skeletal mesh into a rolling move-able kactor ball and

    2. with another button press, turn the kactor ball back into the normal skeletal mesh character!

    3. I provide you with the code to control the pawn in kactor ball form

    4. I provide you with the class you can use to make sure your kactor version of your pawn is properly lit to match the utpawn skeletal mesh component.


    Video

    Here's a video showing my simple algorithm at work



    ~~~

    My Implementation

    1. Upon button press the pawn is made invisible and is made to follow the kactor ball that is spawned, high above in the sky. Collision on pawn is turned off so that if it hits something it will not die.

    2. In ball form, via playertick, the WASD keys are intercepted and sent over to the pawn class to move the ball. The pawn is repositioned every tick using move (not nearly as expensive as SetLocation which rehashes collision as per unreal wikia documentation.

    3. Upon changing back to skeletal mesh form the kactor is destroyed, and normal collision restablished.

    If you prefer to reduce memory modifications, instead of destroying the ball simpley disable it or put it in a container in the level somewhere out of sight, using staticmeshcomponent.setRBPosition()

    ~~~

    Collision Solutions

    The most complicated part of this is getting the collisions to work right as your is changing forms, so that the kactor ball and the skeletal mesh do not overlap and cause weirdness for each other

    The next most complicated thing is the collision on your kactor ball version of your pawn.

    If you make a static mesh version of your skeletal mesh ( with 3ds max for example), if you give that static mesh sphere collision in the UDK the sphere collision is not going to fit very well unless you perfectly shape your static mesh version of your skeletal mesh.

    I found a simple solution though!

    I use a standard engine sphere mesh for the collision, and hide it, and attach the static mesh version of the skeletal mesh as a component without any collision at all.

    This way you can tailor the exact radius of the collision of your static mesh to look the way you want, whereas in the Static Mesh Editor in UDK you cannot easily resize the sphere collision of your static mesh

    ~~~

    So The Solution

    Do not give the static mesh version of your Skeletal Mesh any collision at all, and turn off “per poly” and leave on “uses simple box,line, etc” so that per poly is not activated

    This way your static mesh will not interfere with the simple engine sphere collsion

    ~~~

    How To Make Spherized Static Mesh Version of Skeletal Mesh

    1. I recommend downloading the Student version of 3ds Max, which is free, from the autodesk website.

    They are so very nice to offer this awesome software for free

    http://students.autodesk.com/?nd=download_center
    3ds Max Download From The Official Auto Desk Website

    2. Then, if you were not the artist who made the skeletal mesh, EXPORT the skeletal mesh from UDK as an .fbx

    3. Then IMPORT the .fbx file into 3ds max.

    4. now you can use the container viewer to delete the entire skeleton of the skeletal mesh

    5. use the modifier system of 3ds max, and systematically

    a. use the modifier “mesh select”

    b. apply things like Bend or Spherize, or the FFD 4x4x4 to manipulate the shape of the skeletal mesh parts

    c. to do individual vertex corrects or move parts of the mesh to reposition feet or head etc, use the modifier “Edit Mesh” after using “mesh select”

    In this way you can systematically manipulate the skeletal mesh into the shape you want!
    This is the exact process I used to take my own skeletal mesh, shown in this video, and make the static mesh spherized version

    ~~~

    Skeletal Controllers

    If you look carefully at start of video, I actually manipulate the skeletal mesh into a position smiliar to the static mesh before hiding the pawn.

    This is done with skeletal controllers.

    I will plan on doing a whole tutorial on skeletal controllers at some point soon, but please do internet search for more info on skeletal controllers for now.

    ~~~

    Code For You

    Here's the basic setup you need to do something similar to what you see in the video in your own game!

    Copy Right Policy

    ************************************************** **************
    My CopyRight Policy

    If you use part or all of my algorithms and code please

    give credit where credit is due, by posting a link to my tutorial hub:

    (right click and get link address, this is abbreviated link)
    http://forums.epicgames.com/threads/...de-and-Videos)

    Also post on this page so others see this code as well and can benefit as you have.

    And of course I'd like to know that all the time I've spent preparng these tutorials

    has helped you



    -Rama
    ************************************************** **************

    ~~~

    The Kactor Ball Class That Uses Your Static Mesh

    Code:
    Class RamaSkeletalMeshToBall Extends KActorSpawnable
          Placeable;
    
    var float ballJumpHeight;
    var bool isFalling;
    var float fallingSensitivity;
    
    //vars made global for efficiency
    var float groundZ;
    var float ballradius;
    var float ballheight;
    var float ballBottomZ;
    
    Simulated Event PostBeginPlay() {
    	
    	super.postbeginplay();
    	
    	//set gameinfo, this is my game info
    	//enables me to access the instance of this class
    	//after game starts 
    	YourGameInfo(WorldInfo.Game).RamaSkeletalMeshToBall = Self;
    	
    	//call the below again any time you change/update size of this Object	
    	//retrieves bounding info and puts in these two vars 
    	Self.GetBoundingCylinder(ballheight, ballradius);
    	
    	
    	//SET MASS
    	setMass(0.7);
    	
    	//for tracking RB collisions
    	Self.StaticMeshComponent.ScriptRigidBodyCollisionThreshold = 1;
    	
    }
    
    //Rama's Kactor Set Mass Function 
    //see other tutorials on this at
    // http://forums.epicgames.com/threads/925562-Ever-New-Joy-Tutorial-Hub-(Code-and-Videos)
    function setMass(float newMass) {
    	
    	local RB_BodySetup rbs;
    	
    	//invalid values
    	if (newMass <= 0) return;
    	
    	rbs = new Class'RB_BodySetup';
    	
    	rbs.MassScale = newMass;
    	
    	( Self.StaticMeshComponent.GetRootBodyInstance() ).UpdateMassProperties(rbs);
    		
    }
    
    //My Own Jump Algorithm Part 1
    function jump() {
    	local vector v;	
    	
    	//if ball is not falling
    	if (isFalling) return;
    	
    	
    	v.z = ballJumpHeight;
    	StaticMeshComponent.AddImpulse(v);
    	
    }
    
    //My Own Jump Algorithm Part 2
    function checkIsFalling() {
    	local vector hitLoc;
    	local vector hitNorm;
    	local Actor a;
    	
    	//look straight down for aactor
    	//do a trace from ball directly down to ground below;
    	a = Self.trace(hitLoc, hitNorm, 
    		Location + Vect(0, 0, -1) * 500, Location );
    			
    	//==== Actor found by Trace? ==========
    	
    	if ( a == none) { 
    		isFalling = true;
    		return; 	
    	}
    	
    		//=============
    	
    		//store the height of the ground that is directly below ball
    	groundZ = hitLoc.z;
    	
    	
    		//Get Bottom Z value of dimensions of this object
    		//Location.z calculates from center of object,
    		//we need the bottom z-value of object, where it would touch ground.
    	
    		//this calculation may vary depending on shape of object.
    		//I've only tested it as working perfectly with a sphere-shaped Actor
    	ballBottomZ = abs(Location.z - ballRadius / 2); 
    		
    		//if the bottom of object is not close enough to the ground below in z-axis
    	if (abs(ballBottomZ  - groundZ) > fallingSensitivity ) {
    		isFalling = true;
    	}
    	else isFalling = false;
    		
    
    }
    
    Simulated Function tick( Float DeltaTime ) {
    	
    	super.tick(deltatime);
    	
    	checkIsFalling();
    	
    }
    
    defaultproperties
    {
    	
    	//Light Environment to be like Skeletal Mesh UT Pawn's Light Environment
    	Begin Object Class=DynamicLightEnvironmentComponent Name=joyLightEnv
    		bCastShadows = true
    		
    		bSynthesizeSHLight = true //higher performance cost
    		
    		ModShadowFadeoutExponent = 24
    
    	End Object
    	Components.Add(joyLightEnv)
    	
    	//The visible component, your Skeletal Mesh Made Into Ball-ish Shape
    	Begin Object Class=StaticMeshComponent Name=MetalWarriorSphereCollisionComp
    		
    		//so the lighting matches skeletal mesh
    		LightEnvironment = joyLightEnv
    		CastShadow=true
    		bCastDynamicShadow = true
    		
    		//make sure it is visible to owner
    		bOwnerNoSee=false
    		
    		//for asymetric scaling
    		//scale3d=(x=1.7,y=1.7,z=1.7)
    		StaticMesh = StaticMesh'yourStaticMeshOfSkeletalmesh'
    	
    		//no collision
    		BlockNonZeroExtent	=false
    		BlockZeroExtent		=false
    		BlockActors			=false
    		CollideActors 		=false
    		
    	End Object
    	Components.Add(MetalWarriorSphereCollisionComp)
    	
    	//Invisible Actual Collision
    	Begin Object Name=StaticMeshComponent0
    		//scale
    		scale3d = (x = 0.33, y = 0.33, z = 0.33)
    		
    		//mesh
    		StaticMesh = StaticMesh'EngineMeshes.Sphere'
    		
    		//RB Collisions
    		bNotifyRigidBodyCollision = true
    		
    		//hide
    		HiddenGame = true
    		
    	End Object
    	
    	isFalling = false
    	bCollideActors = true
    
    	//this var may also vary , customize as needed
    	fallingSensitivity = 60 
    	
    	
    	//Jump Speed
    	ballJumpHeight = 3733  
    	//customize to taste, change set mass value in Post Begin Play as needed
    	
    }
    ~~~

    Your Pawn Class

    This is where most of the code is, I've split it into parts for easy reference, but its all once class


    The Global Variables

    Code:
    var yourControllerClass pc;
    var RamaSkeletalMeshToBall RamaSkeletalMeshToBall;
    
    var float ballSpeed;
    var float turnSpeed;
    var float ballAirControl;
    var float nearTopViewAccelFactor;
    var float ballMaxVelocitySize;
    var float ballCurVelocitySize;
    
    var Rotator r;
    var vector vc;
    var vector vc2;
    The Core Functions, turning into and out of ball form

    Code:
    //getting/setting WorldInfo so your classes can communicate
    function setVars() {
    	
    	//create this var in your gameinfo class
    	PC = YourGameInfo(WorldInfo.Game).currentPlayer;
    	
    }
    
    //Post Begin Play
    simulated function PostBeginPlay() {
    	
    	//very important line
    	super.PostBeginPlay();
    
    	//set worldinfo
               //create this var in your gameinfo class
    	YourGameInfo(WorldInfo.Game).currentPawn = Self;
    
    	setTimer(0.1, false, 'setVars' );
    	
    }
    
    simulated function toggleRolling() {
    	
    	if (pc.isRolling) {
    		exitJoyfulRoll();
    	}
    	else enterJoyfulRoll();
    }
    
    event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
    {
       
    	//no take damge while rolling
            //if you find pawn taking damage while entering/leaving rolling
            //use Settimer to extend how long "isRolling" state lasts so pawn
            //no take damage,
    
            //damage intercepted here
            if (pc.isRolling) return;
    	
    	super.TakeDamage(DamageAmount, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
    	
    	
    }
    simulated function enterJoyfulRoll() {
    	
    	//moving? only allow switch when pawn is still
    	if (vsize(velocity) > 4) return;
    	
    	//remove pawn collision so it doesnt hit stuff and die
    	CollisionComponent = none;
    	
    	pc.isRolling = true;
    	
    	//move pawn
    	vc = Location;
    	move(vect(0, 0, 2048));
    	
    	//spawn ball
    
    	//you may not need to adjust the rotation
    	//I've left this in so you can use it if needed
    	r = rotation;
    	//r.yaw -= 16384;
    	//r.roll -= 8000;
    
    	//important to spawn a little in front of skeletal mesh pawn location
    	//or big excitement tends to happen
    
    	//the 8 z units is if you want to raise ball up off ground when it spawns
    	RamaSkeletalMeshToBall = 	Spawn(Class'RamaSkeletalMeshToBall',,, 
    							vc + Vector(rotation) * 32 + vect(0, 0, 8), 
    							r
    						)
    
    	//make ball fall to ground after spawn
    	RamaSkeletalMeshToBall.StaticMeshComponent.WakeRigidBody();
    	
    	//hide pawn
    	setHidden(true);
    }
    
    //================
    
    simulated function exitJoyfulRoll() {
    	
    	//ball destroyed
    	if (RamaSkeletalMeshToBall == none) return;
    	
    
    	//not while falling
    	if (RamaSkeletalMeshToBall != none)
    		if (RamaSkeletalMeshToBall.isFalling)
    			return;
    	
    	//so pawn no longer being moved up in playertick()
    	pc.isRolling = false;
    		
    	//destroy ball
    	RamaSkeletalMeshToBall.destroy();
    		
    	//show pawn and move to location
    	//restore pawn collision
    	CollisionComponent = CylinderComponent;
    		
    	//show pawn
    	sethidden(false);
    		
    	//move pawn
    	//much faster than SetLocation
    	move(RamaSkeletalMeshToBall.Location - Location);
    		
    	//rotate pawn
    	setRotation(pc.Rotation);
    		
    	//make sure pawn reaches ground
    	SetPhysics(PHYS_Falling);
    			
    }
    The Ball Controls

    They are always relative to camera, so whatever direction your player controller / camera is facing, the ball will respond with relative wasd commands just like player pawn.

    If you are using a third person camera and the pc rotation is not what you are using, replace

    Code:
    pc.rotation
    with your third person camera rotation.

    Code:
    function ballMoveInternal() {
    	
    	//no ball
    	if ( RamaSkeletalMeshToBall == none) return;
    	
    	//vc set by each of ballleft,right,etc.
    	
    	//ball dir
    	vc2 = RamaSkeletalMeshToBall.staticmeshcomponent.getrootbodyinstance().GetUnrealWorldVelocity();
    	
    	//going in same dir as told to move?
    	if (vsize(normal(vc) - Normal(vc2)) < 1 ) {
    		
    		//make sure up to date
    		ballCurVelocitySize = Vsize(vc2);
    		
    		//already reached top speed
    		if (ballCurVelocitySize > ballMaxVelocitySize){ 
    			return;
    			//===================
    		}
    	}
    	
    	if (RamaSkeletalMeshToBall.isFalling) {
    		ballAirControl = default.ballAirControl;
    	}
    	else {
    		ballAirControl = 1;
    	}
    	
    	//multiply impulse by speed and air control
    	vc *= ballSpeed * ballAirControl;
    	
    	//apply impulse
    	RamaSkeletalMeshToBall.StaticMeshComponent.AddImpulse(vc * deltaTimeBoostMultiplier);
    	
    	//additional impulse if changing directions
    
    	//makes for smooth fun gameplay
    	if (VSize(Normal(vc) - Normal(RamaSkeletalMeshToBall.StaticMeshComponent.GetRootBodyInstance().velocity)) > 1) {
    		RamaSkeletalMeshToBall.StaticMeshComponent.AddImpulse(vc * turnSpeed * deltaTimeBoostMultiplier);
    	}
    }
    
    function ballLeft() {
    				
    	r = pc.rotation;
    	r.yaw -= 16384; //90 deg
    	
    	//move dir
    	vc = Vector(r);
    	
    	ballMoveInternal();
    }
    
    function ballRight() {
    			
    	r = pc.rotation;
    	r.yaw += 16384; //90 deg
    	
    	vc = Vector(r);
    	
    	
    	ballMoveInternal();
    }
    
    function ballForward() {
    	
    	vc = Vector(pc.rotation);
    	
    	ballMoveInternal();
    	
    	
    }
    
    
    
    function ballBack() {
    	
    	r = pc.rotation;
    	r.yaw += 32768; //180 deg
    	
    	vc = Vector(r);
    	
    	ballMoveInternal();
    	
    }
    
    //to prevent unnecessary excitement
    //you know, like the ball flying across the level into outer space
    //and stuff like that
    function limitBallVelocityMax() {
    	if (MetalWarriorBall == none) return;
    	
    	//if the ball figures out how to prematurely evolve into a super-powered spaceship
    	//and tries to take off such that its velocity > 6000
    	//tell it to chill out for awhile longer before beginning its space voyage
    	if (Vsize(
    		MetalWarriorBall.staticmeshcomponent.getrootbodyinstance().GetUnrealWorldVelocity()) > 
    			6000) {
    			MetalWarriorBall.staticmeshcomponent.setRBLinearVelocity(vect(0, 0, 0));
    		}
    }
    Simulated Event Tick(float DeltaTime)
    {
        Super.Tick(DeltaTime);
    
    	
    	//=== Metal Warrior Ball ====
    	if(pc.isRolling){
    		limitBallVelocityMax();
    	}
    }
    
    defaultproperties
    {
            ballSpeed = 140
    	ballMaxVelocitySize = 2000
    	turnSpeed = 3.8
    	ballAirControl = 0.3
    
    }
    ~~~

    Player Controller Class

    You will need to use key binding to get ahold of the WASD keys, see my other tutorials for more info on this

    Code:
    var bool keyisdownW;
    var bool keyisdownA;
    var bool keyisdownS;
    var bool keyisdownD;
    
    //create functions for keydownW and keyupW that set the boolean as true/false
    //do this for all 4 keys
    
    //then link these exec functions to your defaultinput.ini
    
    //see my third person camera tutorial for more details
    http://forums.epicgames.com/threads/942953-Video-How-to-Make-3rd-Person-Camera-Free-Roam-Camera-Mouse-Cursor-amp-Drag-Select?p=31030597#post31030597 
    
    
    //states
    var bool isRolling;
    
    Simulated Event PlayerTick(Float DT) {
    	//============ SUPER ==================
    	//veeeery important line
    	super.playertick(DT); 
    	//======================================
    
    	//==================
    	//Skeletal Mesh Ball Mode
    	//===================
    	
    		
    	//ROLLING
    	if(isRolling){
    		
    		
    	//not all elses to allow smoother turning
    	if (keyisdownW) {
    		pawn.ballForward();
    	}
    	else if (keyisdownS) {
    		pawn.ballBack();
    	}
    	if (keyisdownA) {
    		pawn.ballLeft();
    	}
    	else if (keyisdownD) {
    		pawn.ballRight();
    	}
    		
    	//make regular pawn follow ball
    	//move is waaay faster than setlocation, setlocation causes collision rehash which causes
    	//it to be visibly slower than move or moveSmooth
    	pawn.move(YourGameinfo(Worldinfo.game).MetalWarriorBall.Location + vect(0, 0, 2048) - pawn.Location);
    		
    	} //END IS ROLLING
    		
    }
    Rama

    #2
    That is some very cool work with code. It really expands udk code knowledge base
    I sure wish I could use it!

    Comment


      #3
      I hope all your tutorials/experiments find their way into a full game, cuz I'd love to play it. Right now I'm imagining psychedelic Super Mario Galaxy + Sonic + Metroid with lots of explosions!

      Comment


        #4
        Originally posted by willyg302 View Post
        I hope all your tutorials/experiments find their way into a full game, cuz I'd love to play it. Right now I'm imagining psychedelic Super Mario Galaxy + Sonic + Metroid with lots of explosions!
        Hahha WillyG that's about what I am going for hee hee


        You know, this video / tutorial was only made possible by your own Kactor tutorials online, your tutorials were the first I ever read about rolling ball kactors






        Rama

        Comment


          #5
          In your code for this tutorial you have, in the pawn class part, a line vc = ballSpeed * ballAirControl;

          but vc is declared as a vector variable. and here it is taking floats it seems to me

          i also get an error for the line following it:

          RamaSkeletalMeshToBall.StaticMeshComponent.AddImpu lse(vc * deltaTimeBoostMultiplier);
          which says 'bad or missing expression after '*': 'deltaTimeBoostMultiplier'
          There's no description of what that's about in the tutorial, so I'm stuck.

          Comment


            #6
            Ahh thanks for the correction!

            code in question:
            Code:
            //multiply impulse by speed and air control
            vc = ballSpeed * ballAirControl;
            the code should be

            Code:
            //multiply impulse by speed and air control
            vc *= ballSpeed * ballAirControl;
            ~~~

            deltaTimeBoostMultiplier

            add a global class var

            Code:
            var float deltaTimeBoostMultiplier;
            and in your tick function

            Code:
            simulated event tick(float deltatime){
              deltaTimeBoostMultiplier = deltatime * 40;
            }
            This my way of accounting for fluctuating frame rates

            The longer the delay between ticks (deltatime) the higher deltaTimeBoostMultiplier will be.



            Rama

            PS: I arrived at the number 40 through a lot of testing, seems to remain useful on different computers

            Comment


              #7
              Firing it up

              Cool - thanks...
              When I fire it up, I don't actually get my KACTORStaticMesh displaying and the skeletalmesh (though it does pop off into the sky) doesn't vanish. The behavior is that I get locked in space way up above the ground.

              What actually fires up the change.
              I'm using G key input event in kismet to fire console command isRolling, and it does change but not as expected.
              I'll post a video shortly.

              Tom

              Originally posted by evernewjoy View Post
              Ahh thanks for the correction!

              code in question:
              Code:
              //multiply impulse by speed and air control
              vc = ballSpeed * ballAirControl;
              the code should be

              Code:
              //multiply impulse by speed and air control
              vc *= ballSpeed * ballAirControl;
              ~~~

              deltaTimeBoostMultiplier

              add a global class var

              Code:
              var float deltaTimeBoostMultiplier;
              and in your tick function

              Code:
              simulated event tick(float deltatime){
                deltaTimeBoostMultiplier = deltatime * 40;
              }
              This my way of accounting for fluctuating frame rates

              The longer the delay between ticks (deltatime) the higher deltaTimeBoostMultiplier will be.



              Rama

              PS: I arrived at the number 40 through a lot of testing, seems to remain useful on different computers

              Comment


                #8
                Originally posted by evernewjoy View Post
                Ahh thanks for the correction!

                code in question:
                Code:
                //multiply impulse by speed and air control
                vc = ballSpeed * ballAirControl;
                the code should be

                Code:
                //multiply impulse by speed and air control
                vc *= ballSpeed * ballAirControl;
                ~~~

                deltaTimeBoostMultiplier

                add a global class var

                Code:
                var float deltaTimeBoostMultiplier;
                and in your tick function

                Code:
                simulated event tick(float deltatime){
                  deltaTimeBoostMultiplier = deltatime * 40;
                }
                This my way of accounting for fluctuating frame rates

                The longer the delay between ticks (deltatime) the higher deltaTimeBoostMultiplier will be.



                Rama

                PS: I arrived at the number 40 through a lot of testing, seems to remain useful on different computers
                Thanks for the code, Rama. I was going through the tutorial when I came across two errors that I just can't figure out. The first is in the pawn class. It says "Error: Bad or missing expression in 'If' " The specific code it is referring is this.

                Code:
                function limitBallVelocityMax() {
                	if (MetalWarriorBall == none) {
                		return;
                	}
                I don't see where MetalWarriorBall is declared, and I don't see any instructions on what I'm suppose to add/change, if anything.

                The second error is even more baffling. "Error: Unrecognized member 'ballForward' in class 'Pawn' "

                Code:
                //not all elses to allow smoother turning
                	if (keyisdownW) {
                		pawn.ballForward();
                	}
                That code looks perfectly fine to me, and yes, the "ballForward" function is declared in my Pawn class. Here is the function.

                Code:
                function ballForward() {
                	
                	vc = Vector(pc.rotation);
                	
                	ballMoveInternal();
                	
                	
                }
                If you have any insight into what is causing these errors, it would be greatly appreciated if you could enlighten me.

                Comment


                  #9
                  In my game, the ball is the player, is there any way I can apply the physics of the ball directly to my player?

                  Comment

                  Working...
                  X