Jump to content

Call animation from a plugin?


Recommended Posts

Hi

So, I've been working on some deployable wheels.. I've written some code to deploy all at the same time, which works perfectly, but I'm stuck trying to call the animation from within the foreach(Repulsor) loop:

        [KSPEvent(guiActive = true, guiName = "Deploy All", active = true)]        public void deploy()
{
foreach (Repulsor rp in this.vessel.FindPartModulesImplementing<Repulsor>())
{
rp.Events["deploy"].active = false;
rp.Events["retract"].active = true;
rp.deployed = true;
print("Deploying");
foreach (WheelCollider wc in rp.GetComponentsInChildren<WheelCollider>())
{
wc.suspensionDistance = rp.Rideheight;
}
}
}//end Deploy All
}//end class

Any ideas? I can call the animation and bind to the gui button with

MODULE{
name = ModuleAnimateGeneric
animationName = Fire
startEventGUIName = Deploy All
endEventGUIName = Retract All
}

in the .cfg file, but this only plays the animation for the selected wheel, not all of them in the vessel. Any hints would be greatly appreciated. I've looked at ModuleAnimateGeneric in Visual Studio, but I can't quite figure out how to call it and apply to 'rp' each time within the loop.

Edited by lo-fi
Link to comment
Share on other sites

Are you trying to call the animation from ModuleAnimateGeneric with your plugin, or are you creating your own animation code and trying to call that?

ModuleAnimateGeneric can only handle playing one animation at a time for a single part. Even if you have multiple modules, with multiple buttons, only one animation will play. I think some of the other animation plugins that are available (Firespitter, Advanced Animator) will allow you to simultaneously play multiple animations on the same part; they still have the multiple button problem though.

You can probably use your plugin to call multiple "deploy" events using those modules.

Link to comment
Share on other sites

Are you trying to call the animation from ModuleAnimateGeneric with your plugin, or are you creating your own animation code and trying to call that?

I was trying to figure out how to call an animation from ModuleAnimateGeneric with my own plugin, but I'm unsure if it's really suitable. I can get an animation to play on the part I've used the button on, but say for example I have four of the same part on this vessel, my plugin code correctly finds all of them with a foreach loop and does it's thing. However, everything I've tried so far results in the animation only playing for the part I used the button on, so I'm clearly doing something wrong. I've tried using the standard Unity animation.Play, but this results in the same thing.

ModuleAnimateGeneric can only handle playing one animation at a time for a single part. Even if you have multiple modules, with multiple buttons, only one animation will play. I think some of the other animation plugins that are available (Firespitter, Advanced Animator) will allow you to simultaneously play multiple animations on the same part; they still have the multiple button problem though.

That may or may not be a problem, depending on the specifics. I'm only trying to trigger one animation on each part, though simultaneously, rather than multiple animations on each part.

You can probably use your plugin to call multiple "deploy" events using those modules.

That's sort of what I've been trying to do but failing miserably at. Been looking through plenty of source, but everyone seems to do it very differently with their own brand of code and I'm a little brain-fried trying to unpick it. Few seem to comment their code very much - not that I want to sound ungrateful it's up there to look at(!), but it does make it harder for a newbie. I think I get what you mean, but could you elaborate slightly?

Grateful for the help :)

Edited by lo-fi
Link to comment
Share on other sites

That's sort of what I've been trying to do but failing miserably at. Been looking through plenty of source, but everyone seems to do it very differently with their own brand of code and I'm a little brain-fried trying to unpick it. Few seem to comment their code very much - not that I want to sound ungrateful it's up there to look at(!), but it does make it harder for a newbie. I think I get what you mean, but could you elaborate slightly?

I wrote some pseudo-code for you. It's untested so it might need some adjustments or have a couple bugs, but it should lead you in the right direction

public class Repulsor : PartModule
{
/* --- your other code ----- */

public void PlayAnimation()
{
// note: assumes one ModuleAnimateGeneric (or derived version) for this part
// if this isn't the case, needs fixing
ModuleAnimateGeneric myAnimation = part.FindModulesImplementing<ModuleAnimateGeneric>().SingleOrDefault();
if (!myAnimation)
{
// this shouldn't happen under normal circumstances
Log.Error("Repulsor animation error: Did not find ModuleAnimateGeneric on {0}", part.ConstructID);
return;
}
else
{
// if another mod were to replace all ModuleAnimateGenerics with
// their own (derived) version and we tried to call the derived
// Toggle method with myAnimation.Toggle(), we'll end up calling
// the base version instead of the most derived version we want.
// If they do any critical logic in there, you've opened yourself
// up to all sorts of bugs or strange behaviour
try
{
// Get the most-derived type and use its Toggle method so we don't
// skip any plugin-derived versions
myAnimation.GetType().InvokeMember("Toggle", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreReturn | System.Reflection.BindingFlags.InvokeMethod, null, myAnimation, null);
}
catch (Exception e)
{
Log.Error("Failed to invoke \"Toggle\" using GetType(), falling back to base type after encountering exception {0}", e);
myAnimation.Toggle();
}
}

}

[KSPEvent(guiActive = true, guiName = "Deploy All", active = true)]
public void deploy()
{
// note: this loop will find "us" too. Intended
foreach (Repulsor rp in this.vessel.FindPartModulesImplementing<Repulsor>())
{
// it's risky to assume all Repulsors are in the same animation state.
// If the player manages to attach a new one with KAS or docks two
// vessels together with repulsors in different states, you can see
// how there could be a problem with just toggling animations on all
// repulsors
if (rp.Events["deploy"].active == this.Events["deploy"].active)
{
print(string.Format("{1} Repulsor attached to {0}", rp.part.ConstructID, rp.Events["deploy"].active ? "Deploying" : "Retracting"));
rp.PlayAnimation();

// was this intended? Once deployed and
// retracted, Repulsor will never be deployable again
//rp.Events["deploy"].active = false;
//rp.Events["retract"].active = true;
//rp.deployed = true;

rp.deployed = !rp.deployed;
rp.Events["deploy"].active = !rp.deployed;
rp.Events["retract"].active = rp.deployed;

// I assume whether the wheel is deployed matters here
foreach (WheelCollider wc in rp.GetComponentsInChildren<WheelCollider>())
wc.suspensionDistance = rp.deployed ? rp.Rideheight : StowedDistance;
//wc.suspensionDistance = rp.Rideheight;
}
}

}//end Deploy All
}

Edited by xEvilReeperx
Link to comment
Share on other sites

Thank you, thank you, thank you! I shall go and study and report back :)

I'd left error handling out for the moment so as not to confuse myself, so thanks for the pointers there too.

Link to comment
Share on other sites

xEvilReeperx - thank you, that was exactly what I needed and you've shown me a lot of useful things in the process. Couple of little kinks to iron out, but it worked more or less straight out of the box. I'll post the full code later as I've got a few questions I'd like to ask if you don't mind, but I need to tidy the unholy mess I made in the process of getting it all together. In the meantime, here is what you've helped me create. It's nowhere near ready, untextured, awaiting a cool anti-grav effect in repulsor mode, the model still needs work and I've got a lot of testing/tweaking to do before I even consider an initial release, but I'm pretty happy so far. The devil is in the detail!

I humbly present my attempt at making rovers practical on low gravity bodies. Bear with it, the interesting bit comes about 30 secs in - these are no ordinary wheels ;)

I name this vessel the Kelorean. Careful hitting 88m/s ;)

(and yes, I did mess up the gui labels, fixing that right now!):

Edited by lo-fi
Link to comment
Share on other sites

Thank you, really appreciate you taking the time to help. Glad you like my crazy invention :)

So, here it is. It adds tweakable suspension settings in the VAB/SPH (based on my tweakable wheel plugin) and in-flight conversion to my anti-grav repulsors:

/* * KSP [0.23.5] Anti-Grav Repulsor plugin by Lo-Fi
* Much inspiration and a couple of code snippets for deployment taken from BahamutoD's Critter Crawler mod. Huge respect, it's a fantastic mod
*
*/




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;


namespace Repulsor
{
[KSPModule("Repulsor")]
public class Repulsor : PartModule
{
public WheelCollider thiswheelCollider; //container for wheelcollider we grab from wheelmodule
public WheelCollider mywc;


[KSPField(isPersistant = true, guiActive = true, guiActiveEditor = true, guiName = "Height"), UI_FloatRange(minValue = 0, maxValue = 2.00f, stepIncrement = 0.25f)]
public float Rideheight; //this is what's tweaked by the line above
[KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Strength"), UI_FloatRange(minValue = 0, maxValue = 3.00f, stepIncrement = 0.2f)]
public float SpringRate; //this is what's tweaked by the line above
[KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Damping"), UI_FloatRange(minValue = 0, maxValue = 1.00f, stepIncrement = 0.025f)]
public float DamperRate; //this is what's tweaked by the line above
[KSPField(isPersistant = true)]
public bool deployed = true;
//forward friction values
[KSPField(isPersistant = true)]
public float forasymSlip;
[KSPField(isPersistant = true)]
public float forasymValue;
[KSPField(isPersistant = true)]
public float forextrmSlip;
[KSPField(isPersistant = true)]
public float forextremValue;
[KSPField(isPersistant = true)]
public float forstiff;
//sideways friction values
[KSPField(isPersistant = true)]
public float sideasymSlip;
[KSPField(isPersistant = true)]
public float sideasymValue;
[KSPField(isPersistant = true)]
public float sideextrmSlip;
[KSPField(isPersistant = true)]
public float sideextremValue;
[KSPField(isPersistant = true)]
public float sidestiff;

//begin start
public override void OnStart(PartModule.StartState start) //when started
{
// degub only: print("onstart");
base.OnStart(start);




if (HighLogic.LoadedSceneIsEditor)
{
foreach (ModuleWheel mw in part.FindModulesImplementing<ModuleWheel>())
{
// mw.steeringMode = ModuleWheel.SteeringModes.ManualSteer;
mw.Events["LockSteering"].guiActiveEditor = false;
mw.Events["DisableMotor"].guiActiveEditor = false;
mw.Events["EnableMotor"].guiActiveEditor = false;
mw.Events["InvertSteering"].guiActiveEditor = false;
mw.Events["DisableMotor"].guiActiveEditor = false; //stop the gui items for wheels showing in editor
}
}
else
{
//ADD CODE HERE TO DEAL WITH WHETHER WE ARE IN WHEEL OR REPULSOR MODE AT START OF FLIGHT!




foreach (ModuleWheel mw in this.vessel.FindPartModulesImplementing<ModuleWheel>())
{


foreach (WheelCollider wc in mw.GetComponentsInChildren<WheelCollider>())
{
// mw.steeringMode = ModuleWheel.SteeringModes.ManualSteer;
mw.Events["LockSteering"].guiActive = false;
mw.Events["DisableMotor"].guiActive = false;
mw.Events["EnableMotor"].guiActive = false;
mw.Events["InvertSteering"].guiActive = false;
mw.Events["DisableMotor"].guiActive = false; //stop the gui items for wheels showing in flight
}
}
}


if (SpringRate == 0) //check if a value exists already. This is important, because if a wheel has been tweaked from the default value, we will overwrite it!
{


thiswheelCollider = part.gameObject.GetComponentInChildren<WheelCollider>(); //find the 'wheelCollider' gameobject named by KSP convention.
mywc = thiswheelCollider.GetComponent<WheelCollider>(); //pull collider properties
JointSpring userspring = mywc.suspensionSpring; //set up jointspring to modify spring property
SpringRate = userspring.spring; //pass to springrate to be used in the GUI
DamperRate = userspring.damper;
Rideheight = mywc.suspensionDistance;
WheelFrictionCurve forwardfric = mywc.forwardFriction;
forasymValue = forwardfric.asymptoteValue;
forextremValue = forwardfric.extremumValue;
forstiff = forwardfric.stiffness;
WheelFrictionCurve sidefric = mywc.sidewaysFriction;
sideasymValue = forwardfric.asymptoteValue;
sideextremValue = forwardfric.extremumValue;
sidestiff = forwardfric.stiffness;



}
else //set the values from those stored in persistance
{
thiswheelCollider = part.gameObject.GetComponentInChildren<WheelCollider>(); //find the 'wheelCollider' gameobject named by KSP convention.
mywc = thiswheelCollider.GetComponent<WheelCollider>(); //pull collider properties
//suspension:
JointSpring userspring = mywc.suspensionSpring; //set up jointspring to modify spring property
userspring.spring = SpringRate;
userspring.damper = DamperRate;
mywc.suspensionSpring = userspring;
//forward friction:
WheelFrictionCurve forwardfric = mywc.forwardFriction;
forwardfric.asymptoteValue = forasymValue;
forwardfric.extremumValue = forextremValue;
forwardfric.stiffness = forstiff;
//sideways friction
WheelFrictionCurve sidefric = mywc.sidewaysFriction;
forwardfric.asymptoteValue = sideasymValue;
forwardfric.extremumValue = sideextremValue;
sidefric.stiffness = sidestiff;

if (deployed == true) //is the deployed flag set? set the rideheight appropriately
{
thiswheelCollider.suspensionDistance = Rideheight;
Events["deploy"].active = false;
Events["retract"].active = true; //make sure gui starts in deployed state
}
else
{
thiswheelCollider.suspensionDistance = Rideheight * 1.5f; //set retracted if the deployed flag is not set
}
}
}//end start




[KSPAction("Toggle Deployed")]
public void AGToggleDeployed(KSPActionParam param)
{
if (deployed)
{
retract();
}
else
{
deploy();
}
}//End Deploy toggle


public void PlayAnimation()
{
// note: assumes one ModuleAnimateGeneric (or derived version) for this part
// if this isn't the case, needs fixing. [COLOR=#0000ff][U]That's cool, I called in the part.cfg[/U][/COLOR]
ModuleAnimateGeneric myAnimation = part.FindModulesImplementing<ModuleAnimateGeneric>().SingleOrDefault();
if (!myAnimation)
{
// this shouldn't happen under normal circumstances
// Log.Error("Repulsor animation error: Did not find ModuleAnimateGeneric on {0}", part.ConstructID);
return; [COLOR=#0000ff][U]//the Log.Error line fails syntax check with 'The name 'Log' does not appear in the current context. I'm unsure quite why, though I've never used Log, only print[/U][/COLOR]
}
else
{
// if another mod were to replace all ModuleAnimateGenerics with
// their own (derived) version and we tried to call the derived
// Toggle method with myAnimation.Toggle(), we'll end up calling
// the base version instead of the most derived version we want.
// If they do any critical logic in there, you've opened yourself
// up to all sorts of bugs or strange behaviour
try
{
// Get the most-derived type and use its Toggle method so we don't
// skip any plugin-derived versions
myAnimation.GetType().InvokeMember("Toggle", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreReturn | System.Reflection.BindingFlags.InvokeMethod, null, myAnimation, null);
} [COLOR=#0000ff][U]// Could you explain what's going on a little more here please? I can't make head or tail of quite what it does. I need to work out how to hide the 'toggle' button on the GUI too.[/U][/COLOR]
catch (Exception e)
{
// Log.Error("Failed to invoke \"Toggle\" using GetType(), falling back to base type after encountering exception {0}", e);
myAnimation.Toggle();
}
}


}


[KSPEvent(guiActive = true, guiName = "Wheel", active = true)]
public void retract()
{
// note: this loop will find "us" too. Intended
foreach (Repulsor rp in this.vessel.FindPartModulesImplementing<Repulsor>())
{
// it's risky to assume all Repulsors are in the same animation state.
// If the player manages to attach a new one with KAS or docks two
// vessels together with repulsors in different states, you can see
// how there could be a problem with just toggling animations on all
// repulsors
//if (rp.Events["retract"].active == this.Events["retract"].active)
if(rp.deployed==true) [COLOR=#0000ff][U]//I couldn't get the line above to work correctly. It only activated the symmetry partner of the part I activated from[/U][/COLOR]
{
print(string.Format("{1} Repulsor attached to {0}", rp.part.ConstructID, rp.Events["retract"].active ? "Retracting" : "Deploying"));
rp.PlayAnimation();


// was this intended? Once deployed and
// retracted, Repulsor will never be deployable again
rp.Events["deploy"].active = true;
rp.Events["retract"].active = false;
rp.deployed = false;


//rp.deployed = !rp.deployed;
//rp.Events["deploy"].active = !rp.deployed;
//rp.Events["retract"].active = rp.deployed;


[COLOR=#0000ff] [U]//Ah, they get toggled again by the opposite routine. I was trying to steer[/U]
[U]//clear of toggling and keep it as 'if this wheel is already deployed' do nothing[/U]
[U]//and move on to deploy any that aren't. [/U][/COLOR]


// I assume whether the wheel is deployed matters here
foreach (WheelCollider wc in rp.GetComponentsInChildren<WheelCollider>())
{
wc.suspensionDistance = rp.Rideheight * 1.75f;
WheelFrictionCurve sidefric = wc.sidewaysFriction;
sidefric.asymptoteValue = 0.001f;
sidefric.extremumValue = 0.001f;
sidefric.stiffness = 0f;
wc.sidewaysFriction = sidefric;
//debug only: print(wc.sidewaysFriction.asymptoteValue);
//debug only:print(wc.sidewaysFriction.extremumValue);

WheelFrictionCurve forwardfric = wc.forwardFriction;
forwardfric.asymptoteValue = 0.001f;
forwardfric.extremumValue = 0.001f;
forwardfric.stiffness = 0f;
wc.forwardFriction = forwardfric;
//debug only: print(wc.forwardFriction.asymptoteValue);
//debug only: print(wc.forwardFriction.extremumValue);
}
}
}


}//end Deploy All


[KSPEvent(guiActive = true, guiName = "Repulsor", active = true)]
public void deploy()
{
// note: this loop will find "us" too. Intended
foreach (Repulsor rp in this.vessel.FindPartModulesImplementing<Repulsor>())
{
// it's risky to assume all Repulsors are in the same animation state.
// If the player manages to attach a new one with KAS or docks two
// vessels together with repulsors in different states, you can see
// how there could be a problem with just toggling animations on all
// repulsors
//if (rp.Events["deploy"].active == this.Events["deploy"].active)
if(rp.deployed==false)
{
print(string.Format("{1} Repulsor attached to {0}", rp.part.ConstructID, rp.Events["deploy"].active ? "Deploying" : "Retracting"));
[COLOR=#0000ff][U]//This line is really helpful. I was going to work out how to print part unique identifiers[/U][/COLOR]
rp.PlayAnimation();


// was this intended? Once deployed and
// retracted, Repulsor will never be deployable again
rp.Events["deploy"].active = false;
rp.Events["retract"].active = true;
rp.deployed = true;


//rp.deployed = !rp.deployed;
//rp.Events["deploy"].active = !rp.deployed;
//rp.Events["retract"].active = rp.deployed;


foreach (WheelCollider wc in rp.GetComponentsInChildren<WheelCollider>())
{
// wc.suspensionDistance = Rideheight;
wc.suspensionDistance = rp.Rideheight;
WheelFrictionCurve forwardfric = wc.forwardFriction;
forwardfric.asymptoteValue = forasymValue;
forwardfric.extremumValue = forextremValue;
forwardfric.stiffness = forstiff;
wc.forwardFriction = forwardfric;
//debug only: print(wc.forwardFriction.asymptoteValue);
//debug only: print(wc.forwardFriction.extremumValue);


//sideways friction
WheelFrictionCurve sidefric = wc.sidewaysFriction;
sidefric.asymptoteValue = sideasymValue;
sidefric.extremumValue = sideextremValue;
sidefric.stiffness = sidestiff;
wc.sidewaysFriction = sidefric;
//debug only: print(wc.sidewaysFriction.asymptoteValue);
//debug only: print(wc.sidewaysFriction.extremumValue);
}


}
}


}//end Deploy All





}//end class
} //end namespace


I've underlined my comments in the code with questions in blue and left yours in for clarity where I've used your snippets. Learned a lot and sorted quite a few things that have been bugging me along the way. The WheelTweak plugin is rather simple in comparison! I'm sure there are better ways to write some of my code, but it works and I need to absorb it a little more before I change too much else. Baby steps, I'm very new to this. Anywhere I've really fallen flat on my face? I have yet to test for docking etc, though I got pretty good at making sure load/save/switch vessel worked correctly. KAS needs testing too.

Again, thank you - you did indeed point me in the right direction.

Link to comment
Share on other sites

//      Log.Error("Repulsor animation error: Did not find ModuleAnimateGeneric on {0}", part.ConstructID);
return; [COLOR=#0000ff][U]//the Log.Error line fails syntax check with 'The name 'Log' does not appear in the current context. I'm unsure quite why, though I've never used Log, only print[/U][/COLOR]

I use my own debug routines. That's equivalent to Debug.LogError(string.format("Repulsor etc etc {0}", ...). Sorry, I got used to working with some of my own convenience classes

try
{
// Get the most-derived type and use its Toggle method so we don't
// skip any plugin-derived versions
myAnimation.GetType().InvokeMember("Toggle", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreReturn | System.Reflection.BindingFlags.InvokeMethod, null, myAnimation, null);
} [COLOR=#0000ff][U]// Could you explain what's going on a little more here please? I can't make head or tail of quite what it does. I need to work out how to hide the 'toggle' button on the GUI too.[/U][/COLOR]
catch (Exception e)
{
// Log.Error("Failed to invoke \"Toggle\" using GetType(), falling back to base type after encountering exception {0}", e);
myAnimation.Toggle();
}

A simple myAnimation.Toggle() would work in almost all cases. The problem the above solves is if somebody had a mod that replaced all "ModuleAnimateGeneric" PartModules with a derived version using something like ModuleManager. I'm sure you've noticed there are a lot of potential replacements out there.

Essentially, what the line does is to use reflection to search through the real underlying type for a method with the name specified and then calls it. It will almost always resolve to ModuleAnimateGeneric.Toggle, but should anyone replace the ModuleAnimateGeneric in your part with an "improved" version, that method will be run as the derived version author expects and your code won't break because you accidentally called the wrong method and skipped any extra logic the derived version implemented in its version of Toggle. It's just a bit of a safety net.

//if (rp.Events["retract"].active == this.Events["retract"].active)
if(rp.deployed==true) [COLOR=#0000ff][U]//I couldn't get the line above to work correctly. It only activated the symmetry partner of the part I activated from[/U][/COLOR]

Just a bug; your way is fine (and better)

[COLOR=#0000ff][U]//Ah, they get toggled again by the opposite routine. I was trying to steer[/U]
[U]//clear of toggling and keep it as 'if this wheel is already deployed' do nothing[/U]
[U]//and move on to deploy any that aren't. [/U][/COLOR]

Well, I was going by what the state of the wheel was and targeting other wheels that didn't match that state. The player would have clicked on "Retract all" if the wheel was already deployed (based on my understanding of your initial snippet), so I figured it'd make the most sense to retract any deployed wheels rather than deploy the undeployed ones.

You could give every wheel a "deploy all" and "retract all" event that's active all the time or you could make sure every wheel's "deploy all" stays active until all wheels are deployed.

Link to comment
Share on other sites

Perfect, that clears pretty much everything up. :)

I'll do some testing and muse on the deploy/retract all issue; you're right, it needs some work.

My next challenge will be to play with transforms and forces to implement anti-roll bar simulation. Thankfully, there is lots of Unity documentation there to draw on so I hope that one will not be too hard. Pairing up wheels on each side will be the trickiest bit, I think, given the freedom of design KSP allows. I'll post up as things progress. Looking forward to release day and seeing what people do with my creations.

Owe you a beer! :D

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...