So, how do you manage (quite a lot) small components?
Currently struggling with Player ability which keeps added with new components to handle small things. (PlayerJump, PlayerMove, PlayerInteract, and so on)
I’m thinking about making a RegisterMe method inside PlayerController, but getting stuck figuring out how to pass itself(the caller component) inside a method and assign it to its specific class variable slot.
You’ll have to elaborate. What do you mean by “spawning new components”? If it’s for player behaviour(s), do they have to be actual components compared to plain old classes?
RegisterMe is good, I’ve used that. The other way is to have a master script that adds other scripts in its start method. This works nicely because the master script can give the other scripts exactly what they need to do business.
I like this pattern for adding components at runtime:
Factory Pattern in lieu of AddComponent (for timing and dependency correctness):
Good naming of your parts helps a lot too, but even then it can get impressively long and complex.
This is the “start up the player spaceship controller” code for my Jetpack Kurt Space Flight Game, which has now been in active development for nine (9) years and has seen over 6000 commits to the master branch. It’s not pretty but it works and I still regularly update it.
IEnumerator Start()
{
MyInput.Instance.SetInputGameMode( MyInput.InputGameMode.PLAYING_SPACE);
// just to ensure they are updated
DSM.SpaceFlightVelocityY.Clear();
DSM.SpaceFlightVelocityX.Clear();
DSM.SpaceFlightRadarAltimeterString.Clear();
// turn off helicopter mode if you don't have previews on, just in case
if (!DSM.Settings.EnablePreviewLevels.bValue)
{
DSM.SpaceFlightEnableHelicopterMode.bValue = false;
}
WeatherController.InitGame();
DSM.SpaceFlightPowerLevel.fValue = 0.0f;
DSM.SpaceFlightEnginePSLevel.fValue = 0.0f;
Game1.StartMissionTimers();
FlightDataRecorder.Instance.StartGame();
ScoreAdvanceTable.ResetBonusesForNextLegOfTravel();
cam = Camera.main;
FocusWatchPauseOnDeviceOnly.Create();
var esc = gameObject.AddComponent<Escape>();
esc.NeedConfirmation = true;
esc.NeedPauseOverlay = true;
esc.OnEscape = () => {
DSM.SpaceFlightZeroLandingGameResult.Value = "Abandoned";
DSM.SpaceFlightNotifyMissionInterrupted.Poke();
Game1.GameOver( MissionStatusType.Abandoned);
};
if (configure.Instance.CTRLR)
{
esc.InTVMode = true;
}
results.EnableNextButton = false;
yield return null;
CreateVABS();
OrientationChangeSensor.Create( transform,
() => {
CreateVABS();
InitiateLabelHidingTimer();
}
);
// angles camera down when weapons are equipped, versus using camera oblique frustum
SpaceFlightCameraObliquity.UseXAngleVersusObliquity = DSM.Combat.EquipPlayerWithWeapons.bValue;
SetCameraObliquity();
OrientationChangeSensor.Create( transform, SetCameraObliquity);
SpaceFlightGravitySetting.Current.ApplyToPhysicsSystem();
GameObject player = new GameObject( "SpaceFlightPlayer");
STATE.ThePlayer = player;
GameObject visible = null;
{
int shipno = 0; // DSM.SpaceFlightSelectedPlayerShip.iValue;
if (DSM.SpaceFlightOverridePlayerShip.bValue)
{
shipno = DSM.SpaceFlightNewGame.SelectedPlayerShip.iValue;
}
shipno = PossiblePlayerShips.ConstrainIndex( shipno);
GameObject prefab = PossiblePlayerShips.GameObjects[ shipno];
visible = Instantiate<GameObject>(prefab, player.transform);
if (DSM.Settings.EnableVisibleControlGeometry.bValue)
{
if (shipno < PlayerShipControls.Length)
{
var ctrls = Instantiate<GameObject>( PlayerShipControls.GameObjects[shipno], player.transform);
visibleControls = ctrls.GetComponent<SpaceFlightControlsGeometry>();
}
}
}
JetblastNozzle = new GameObject( "JetblastNozzle").transform;
JetblastNozzle.SetParent( player.transform);
JetblastNozzle.localRotation = Quaternion.Euler( 90, 0, 0);
JetblastNozzle.localPosition = Vector3.zero; // driven elsewhere depending on camera
jetblast_feeler.Attach( JetblastNozzle,
() =>
{
float amount = 30.0f * indicatedPower;
return amount;
},
() =>
{
return 8.0f;
}
);
InitialCameraSetup( player, visible);
Transform spawn = null;
while( spawn == null)
{
yield return null;
spawn = ProcessPlayerSpawnsAndObjectives();
}
MoteCentroid = new GameObject ("MoteCentroid").transform;
MoteCentroid.SetParent (player.transform);
MoteCentroid.localPosition = Vector3.forward * 10.0f; // always first person!
MoteSystem.Create(
MoteCentroid,
() => {
if (rb)
{
return rb.velocity;
}
return Vector3.zero;
},
MasterSizeScale: 0.3f,
MasterCountScale: 2.0f
);
Debug.Log( "Choosing spawn named " + spawn.name);
Ray ray = new Ray( spawn.position + Vector3.up * 20, Vector3.down);
RaycastHit rch;
Vector3 pos = spawn.position;
if (Physics.Raycast( ray, out rch, 100))
{
pos = rch.point + Vector3.up * 2.0f;
}
player.transform.SetPositionAndRotation( pos, spawn.rotation);
rb = player.AddComponent<Rigidbody>();
// it's fucking space vacuum baby!!
rb.angularDrag = 0.0f;
rb.drag = 0.0f;
rb.centerOfMass = Vector3.zero;
STATE.ThePlayerRB = rb;
GenericImpactSensor.Attach(
rb,
OnImpactImpulse,
() => {
if (!ready) return false;
if (YouHaveDied) return false;
return true;
},
OnceOnly: false
);
DamagePropertyImpactSensor.Attach(
rb,
(deadlinessType) => {
if (!ready) return;
if (YouHaveDied) return;
TrackAppliedDamage(100);
MissionLogEntryCollection.Add( MissionLogType.SHIP_DAMAGED, "Deadly contact");
YouAreDead();
switch( deadlinessType)
{
default :
break;
case DeadlinessType.SHAKING :
PlayerType1DemiseShaking.Attach( rb.gameObject, alsoGameOver: false);
break;
case DeadlinessType.ELECTROCUTION :
PlayerType1DemiseElectrocution.Attach( rb.gameObject, alsoGameOver: false);
PlayerType1DemiseShaking.Attach( rb.gameObject, alsoGameOver: false);
break;
}
},
() => {
return true;
}
);
BumpNoiseImpactSensor.Attach(
rb,
() => {
if (!ready) return false;
// doesn't matter if you died; we still keep making sound!
return true;
}
);
FuelConsumer = SpaceFlightFuelConsumer.Attach( gameObject);
if (SpaceFlightFuelConfig.DoesNeedPurelyFuelBases())
{
System.Func<Color> GetRadarColor = () =>
{
bool blink = (((Time.time * 3) % 1.0f) < 0.5f);
if (FuelConsumer.IsLowFuelCritical)
{
return blink ? Color.yellow : Color.red;
}
if (FuelConsumer.IsLowFuelWarning)
{
return blink ? Color.yellow : Color.black;
}
return Color.cyan;
};
GenericSiteSpawner.PlaceSpaceFlightFuel(
spawn, spawn.position, GetRadarColor);
yield return null;
}
DSM.SpaceFlightDigitalFuelSeconds.Clear();
DSM.SpaceFlightDigitalFuelFormatting.Clear();
DSM.SpaceFlightDigitalFuelColor.Clear();
CreateAppropriateInstruments();
DSM.SpaceFlightEngineStatus.iValue = (int)SpaceFlightEngineStatusType.NORMAL;
DSM.SpaceFlightEngineOnLatch.bValue = false;
SpaceFlightRadarAltimeter.Create( rb);
SpaceFlightHUD2.Create( rb);
SpaceFlightScoreHUD.Create();
if (DSM.Settings.EnableVelocitySlope.bValue)
{
SpaceFlightVelocitySlopeIndicator.Create();
}
fpsf = FPSFollow.Attach (cam.gameObject, null);
fpsf.enabled = true;
DisableRenderersWhenPlayerNear.PlayerTransform = player.transform;
yield return null;
LoadingOverlay.SetActive(false);
if (DSM.SpaceFlightEnableHelicopterMode.bValue)
{
var rotor_attach_point = new GameObject( "rotor_attach_point");
rotor_attach_point.transform.SetParent( player.transform);
rotor_attach_point.transform.localPosition = Vector3.up * 1.0f;
rotor_attach_point.transform.localRotation = Quaternion.identity;
System.Func<Quaternion> GetOrientation = () => {
return rotorDiscVisualTilt;
};
System.Func<float> GetRPM = () => {
return indicatedRPM;
};
RotorSystem1.Attach( rotor_attach_point.transform, GetOrientation, GetRPM);
}
System.Action OnFinishedIntroCamera = () => {
ready = true;
STATE.PlayerTransform = player.transform;
CommonStatisticsAccumulator.Attach();
SpaceFlightMissionDecider.StartProperMission();
{
MissionLogEntryCollection.Add( MissionLogType.ANNOTATION, SpaceFlightFuelConfig.FullCurrentlySelectedFuelDescription());
}
{
MissionLogEntryCollection.Add( MissionLogType.ANNOTATION, SpaceFlightGravitySetting.FullCurrentlySelectedGravitySettingDescription());
}
SETTINGS.MissionLogCreateWeatherReport();
SpaceFlightRadar1.SupplyPlayerTransform( CameraSwivel, cam);
InitiateLabelHidingTimer();
DSEnableShadowsOnLight.Create();
ThumbPositionReminder.Load();
SpaceFlightProximityRefuelingSensor.SetDelegatesForAll( GetPlayerPosition, GetIsLanded);
if (DSM.SpaceFlightEnablePitchLadder.bValue)
{
SceneHelper.LoadScene( "SpaceFlightPitchLadderHUD", additive: true);
}
ConfigureAppropriateCameraMode();
if (trigger1) trigger1.enabled = true;
SaveSystem.Instance.MarkAttempted();
spaceflight_tv_dpad_briefing.Load(); // all guarded for editor, TV, etc.
};
DSEnableShadowsOnLight.Create();
{
if (trigger1) trigger1.enabled = false;
IntroductoryCamera.Create(
() => {
return cam.gameObject.transform;
},
(fr) => { indicatedRPM = fr; },
OnFinishedIntroCamera
);
}
PulseRedDamage.Create (() => {
return RedFraction;
});
SpaceFlightDarkNightMode.ConditionallyAttach( player.transform);
SpaceFlightObscurationMode.ConditionallyAttach();
if (HighLevelGameModeUtility.IsPlanetary())
{
SpaceFlightEngineMode.SelectPlanetaryEngineMode();
NotifyWhenDisabled.Attach( gameObject,
(goDummy) => {
SpaceFlightStabilityAugmentationUtility.SelectTutorialStability();
SpaceFlightEngineMode.SelectTutorialEngineMode();
SpaceFlightEngineVariabilityConfig.SelectTutorialEngineVarability();
}
);
DSM.SpaceFlightStabilityAugmentationLevel.iValue = (int)SpaceFlightStabilityAugmentationType.NONE;
// not sure this is good
// DSM.SpaceFlightEnableInflightEngineStop.bValue = true;
}
}
If you are using AddComponent to add a component, then that call returns a reference to the component instance you just added. You can do whatever you want with it. Depending on how your stuff is setup, if the smaller components all use the same interface or inherit from a base class, a single method that you pass things to can work.
If that isn’t the case, you can still just pass it over for assignment after using AddComponent.
pardon, my bad at choosing words. It’s just like (PlayerInteract, PlayerJump, PlayerMove, etc) & not actually spawning a component. I’m not really familiar with working with a non-component script for now. But the functionality is mostly related to behavior.
Gotta be honest I’m not at that level yet to understand how to implement Factory patterns, will look into it later on.