个性化Player

相比昨天主要是完整实现了角色的异化,来看看具体的思路吧。

首先,因为 PlayerInputManager 不支持异化,只能构造通用的 Player,所以就没法直接让 PlayerInputManager 根据不同的 PlayerIndex来 实例化不同的 Player。

所以想到可以让 PlayerController 直接管理一个 static list 来存储所有的异化 Player,然后再通过 PlayerIndex 来获取对应的 Player,但是这样破坏了封装性。

干脆就直接让 PlayerController 直接获取对应的 PlayerSpecific,然后拷贝或者创建对应的组件,所以我们就可以在创建中创建一个 PlayersSpecifics 的空物体,然后在里面放置所有的 PlayerSpecific。

再通过下面的代码,为每一个 PlayerIndex 查找对应的 PlayerSpecific,如果找不到就使用默认的 PlayerSpecific。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void Start()
{
Debug.Log("Start: " + _playerIndex);

var playersSpecifics = GameObject.FindGameObjectWithTag("PlayersSpecifics");
if (playersSpecifics == null)
{
Debug.LogError("PlayersSpecifics not found, please add it to the scene");
return;
}

var playerSpecific = playersSpecifics.transform.GetChild(_playerIndex).gameObject;
if (playersSpecifics == null)
{
Debug.LogWarning("PlayerSpecific: " + _playerIndex + " not found, using default(0)");
playerSpecific = playersSpecifics.transform.GetChild(0).gameObject;
}

// Copy all components from the playerSpecific prefab to this instance
{
transform.position = playerSpecific.transform.position;
transform.rotation = playerSpecific.transform.rotation;
transform.localScale = playerSpecific.transform.localScale;

foreach (var component in playerSpecific.GetComponents<Component>())
{
if (component is Transform) continue;
CopyComponent(component, gameObject);
}
}

// Initialize special variables
{
_colour = playerSpecific.GetComponent<SpriteRenderer>().color;
Debug.Log("Player" + _playerIndex + " colour: " + _colour);
}
}

找到之后就需要为我们的 Player 实例拷贝或创建所有特殊的组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void CopyComponent(Component original, GameObject destination)
{
var type = original.GetType();
if (destination.TryGetComponent(type, out var copyComponent))
Debug.Log("Component already exists: " + type.Name);
else
copyComponent = destination.AddComponent(type);

foreach (var field in type.GetFields(BindingFlags.Instance))
{
Debug.Log("Copying field from " + original.GetType().Name + ": " + field.Name);
field.SetValue(copyComponent, field.GetValue(original));
}

foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.DeclaredOnly))
{
if (!property.CanWrite || property.Name == "name") continue;

Debug.Log("Copying property: " + property.Name);
property.SetValue(copyComponent, property.GetValue(original));
}
}

给大家看一下 Log,由于我设置了 BindingFlags.DeclaredOnly,所以只会遍历当前 GameObject 特有的 Property,所以只有 SpriteRenderer,可以看到就是把所有属性都拷贝了。

综上所述,之后只需要把通用的直接挂载在 Player Perefab,把特殊的分别在每个 PlayerSpecific 就行了,比如之后有不同的武器系统,都实现起来很方便。

Field and Property

Field 和 Property 是 C# 中两种不同的成员变量定义方式,如下。

1
2
3
4
5
6
7
8
9
10
11
12
public class Person
{
// Private field
private string _name;

// Property
public string Name
{
get { return _name; } // Returns the value of the private field
set { _name = value; } // Sets the value of the private field
}
}