本篇将详细讲解第10篇日志中的Prefab与GameComponent部分(一定要先看第10篇)
如果说上一篇决策层的Brain和EventController是适用于其它生物和人物的话,那么本篇的Prefab和GameComponent就适用于整个游戏世界中所有物体(一颗树,一个斧头,一头狼,以及玩家角色)。这也是为什么角色和其它生物的决策层和动画层都要依靠GameComponent来实现功能,将整个游戏中的功能性代码与数据都集中在GameComponent中,模糊了生物与非生物的界限,也大大提高了代码的重用性以及debug方便性。至于GameComponent的特点以及意义在第10篇中已经说的很清楚了
public abstract class GameComponent : MonoBehaviour
{
/*public bool Active
{
get => active;
}
private bool active;
public void SetActive()
{
active = true;
}
public void SetInactive()
{
active = false;
}*/
public virtual void InitData(Dictionary<string, object> data)
{
}
public virtual void SaveData(Dictionary<string, object> data)
{
}
}
可以看到我在GameComponent中几乎什么都没做!从之前提到的特征也能看出实际上用传统的Component(MonoBehaviour)也完全可以,但为什么要创建单独的GameComponent
public static T AddGameComponent<T>(this GameObject gameObject) where T : GameComponent
{
T temp = gameObject.AddComponent<T>();
return temp;
}
每个游戏物体的包含一个PrefabComponent,它就像身份证是唯一能证明斧子是斧子的组件,它非常重要,主要负责以下事情
不难看出,PrefabComponent最主要的任务集中在游戏开始时和结束时,但更重要的是,因为一定所有游戏物体都带有该组件,我可以将标签(GameTag)等这样的全局特性应用到PrefabComponent上(未来也许会有更多的属性)
[FlagsAttribute]
public enum GameTag
{
creature = 0x0001,
player = 0x0002,
neutral = 0x0004,
evil = 0x0008
}
public abstract class PrefabComponent : MonoBehaviour
{
public class PersistentPrefabData
{
//pos
public float x;
public float y;
public float z;
//rot
public float pitch;
public float yaw;
public float roll;
//AssetName(AssetBundle)
public string loadPath;
//all data of gamecomponents it have
public Dictionary<string, object> componentData;
void Generate()
{
GameObject target = ABManager.Instance.LoadAsset<GameObject>("Prefab", loadPath);
GameObject go = GameObject.Instantiate(target, new Vector3(x, y, z), Quaternion.Euler(pitch, yaw, roll));
PrefabComponent pc = go.GetComponent<PrefabComponent>();
pc.Init(componentData);
}
}
public abstract string bundleName {get;}
private GameTag gameTag;
public void Init(Dictionary<string, object> componentData)
{
DefaultInit();
if(componentData != null) InitComponentsData(componentData);
}
void Awake()
{
Init(null);
}
public void AddTag(GameTag tag)
{
gameTag |= tag;
}
public bool HasTag(GameTag tag)
{
return (gameTag & tag) != 0;
}
public string GetPersistentData()
{
PersistentPrefabData data = new PersistentPrefabData();
data.x = transform.position.x;
data.y = transform.position.y;
data.z = transform.position.z;
data.pitch = transform.rotation.eulerAngles.x;
data.yaw = transform.rotation.eulerAngles.y;
data.roll = transform.rotation.eulerAngles.z;
data.loadPath = bundleName;
foreach (var item in GetComponents<GameComponent>())
{
item.SaveData(data.componentData);
}
return JsonConvert.SerializeObject(data);
}
public abstract void DefaultInit();
public void InitComponentsData(Dictionary<string, object> componentData)
{
foreach (var item in GetComponents<GameComponent>())
{
item.InitData(componentData);
}
}
}
举两个例子看一下PrefabComponent具体是如何工作的
石头人
public class GolemEarth : PrefabComponent
{
private GolemEarthBrain brain;
public override string bundleName
{
get => "GolemEarth";
}
public override void DefaultInit()
{
EventHandler eventHandler = gameObject.AddComponent<EventHandler>();
eventHandler.ListenEvent("hasmineraround", OnHasMinerAround);
ActionController actionController = gameObject.AddComponent<ActionController>();
//actionController.debug = true;
Locomotor locomotor = gameObject.AddGameComponent<Locomotor>();
locomotor.defaultMoveSpeed = Constants.golem_move_speed;
locomotor.debug = true;
RememberLocation rememberLocation = gameObject.AddGameComponent<RememberLocation>();
rememberLocation.Remember("spawnpoint", transform.position);
Combat combat = gameObject.AddGameComponent<Combat>();
InitCombat(combat);
Health health = gameObject.AddGameComponent<Health>();
health.maxHealth = Constants.golem_health;
health.health = health.maxHealth;
brain = gameObject.AddComponent<GolemEarthBrain>();
//brain.debug = true;
brain.Begin();
}
private void InitCombat(Combat combat)
{
Combat.Config defau = new Combat.Config();
defau.attackCD = Constants.golem_attack_cd;
defau.attackDistance = Constants.golem_attack_distance;
defau.hitDistance = Constants.golem_hit_distance;
defau.attackDuration = Constants.golem_attack_duration;
defau.damage = Constants.golem_attack_damage;
Combat.Config spin = new Combat.Config();
spin.attackCD = Constants.golem_spin_cd;
spin.attackDistance = Constants.golem_spin_attack_distance;
spin.hitDistance = Constants.golem_spin_hit_distance;
spin.attackDuration = Constants.golem_spin_attack_duration;
spin.damage = Constants.golem_spin_attack_damage;
combat.CreateConfig("default", defau);
combat.CreateConfig("spinattack", spin);
combat.minAttackGap = Constants.normal_min_attack_gap;
}
private void OnHasMinerAround(params object[] args)
{
if(GetComponent<Combat>().IsValidTarget()) return;
GameObject target = args[0] as GameObject;
gameObject.GetComponent<Combat>().target = target;
}
}
石头人是非常简单的生物,像Combat这样的组件基本生物都有而且初始化项很多,也有些组件不需要初始化,除此外还注册了聆听周围采矿事件的处理
镐子
public class Pickaxe : PrefabComponent
{
public override string bundleName
{
get => "Pickaxe";
}
public override void DefaultInit()
{
AnimatorOverrideController pickaxeAnimator = ABManager.Instance.LoadAsset<AnimatorOverrideController>(bundleName, "OverrideAnim");
Sprite inventoryIcon = ABManager.Instance.LoadAsset<Sprite>(bundleName, "InventoryIcon");
Pickable pickable = gameObject.AddGameComponent<Pickable>();
pickable.type = PickType.Pickup;
InventoryItem inventoryItem = gameObject.AddGameComponent<InventoryItem>();
inventoryItem.icon = inventoryIcon;
Weapon weapon = gameObject.AddGameComponent<Weapon>();
Combat.Config conf = new Combat.Config();
conf.damage = Constants.pickaxe_damage;
conf.attackDuration = Constants.tool_attack_duration;
conf.attackDistance = Constants.tool_attack_distance;
conf.hitDistance = Constants.tool_hit_distance;
weapon.combatConf = conf;
Tool tool = gameObject.AddGameComponent<Tool>();
tool.toolTypes = new List<WorkToolType>{ WorkToolType.Pickaxe };
FiniteUse finiteUse = gameObject.AddGameComponent<FiniteUse>();
finiteUse.maxUse = 50;
finiteUse.currUse = 50;
Equipable equipable = gameObject.AddGameComponent<Equipable>();
equipable.equipSlotType = EquipSlotType.Hand;
equipable.overrideAnimator = pickaxeAnimator;
equipable.pos = new Vector3(0.005f, -0.019f, 0.38f);
equipable.rot = new Quaternion(0.5f, -0.5f, -0.5f, 0.5f);
Inspectable inspectable = gameObject.AddGameComponent<Inspectable>();
inspectable.inspectStr = "nxsnb,nxsnb nxsnxs";
inspectable.rimLightMat = ABManager.Instance.LoadAsset<Material>("Material", Constants.default_rimLightMat_path);
}
}
对于非生物而言,没有EventController和ActionController,也没有brain,其它没有区别。PrefabComponent内一般不包含需要持久化的字段。