Created
April 5, 2017 16:31
Star
You must be signed in to star a gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System; | |
using UnityEngine.Networking; | |
using UnityEngine.Events; | |
using UnityEngine.Assertions; | |
using Random = UnityEngine.Random; | |
using System.Linq; | |
public class Unit : NetworkBehaviour | |
{ | |
//[SyncVar] | |
public uint playerId; | |
public int hp; | |
public int hpMax; | |
[SyncVar(hook="ChargeHook")] | |
public float charge; | |
public bool isCharged; | |
public int speed; | |
public int weapon; | |
public int attackPower; | |
public int defense; | |
public int moveRange; | |
public int jumpRange; | |
public int level; | |
public int job; | |
public GlobalDirection direction; | |
public UnitSkill skill; | |
public UnitMovement movement; | |
public Animator animator; | |
public Model3D model; | |
public Point point; | |
public UnitSMState state; | |
public List<GameObject> uis = new List<GameObject>(); | |
string _debugString; | |
string _debugString2; | |
// individual unit events | |
public FloatFloatUnityEvent ChargeChanged = new FloatFloatUnityEvent(); // first value is current charge, 2nd value is max charge (i.e. 'speed') | |
public IntIntIntUnityEvent HpChanged = new IntIntIntUnityEvent(); // first value is old hp, 2nd value is new hp, 3rd value is direction | |
// static unit events – all units invoke these events and pass along their id, making it easy for other parts | |
// of the system to observe general unit behaviour (such as checking for game over conditions when any unit | |
// dies vs looping in a updatee somewhere... QUESTION maybe the complexity doesn't outweigh the benefits though) | |
public static UIntUnityEvent Charged = new UIntUnityEvent(); | |
public static UIntUnityEvent Died = new UIntUnityEvent(); | |
public override void OnStartAuthority () | |
{ | |
base.OnStartAuthority(); | |
// add self to field as soon as we are authorized *and* initialized | |
StartCoroutine(OnAuthorized()); | |
} | |
IEnumerator OnAuthorized() | |
{ | |
while(!_init) | |
yield return new WaitForEndOfFrame(); | |
RefreshNetUI (); | |
RefreshParams (); | |
CmdFwdFieldSetValue (point, netId.Value); | |
} | |
bool _init; | |
void Awake() | |
{ | |
movement = GetComponent<UnitMovement>(); | |
skill = GetComponent<UnitSkill>(); | |
animator = GetComponent<Animator>(); | |
model = GetComponentInChildren<Model3D>(); | |
} | |
public override void OnStartClient() | |
{ | |
// PlayerIdHook(playerId); | |
ChargeHook(charge); | |
} | |
IEnumerator Start() | |
{ | |
while(netId.Value == 0) | |
{ | |
_debugString = string.Format("Unit.Start: netId is zero, yielding frame", ""); | |
ClientDebugUI.i.Log (_debugString, GameColor.Magenta); | |
yield return new WaitForEndOfFrame(); | |
} | |
while(playerId == 0) | |
{ | |
// var player = GetComponentInParent<Player> (); | |
// if (player != null && player.netId.Value > 0) { | |
// playerId = player.netId.Value; | |
// continue; | |
// } | |
_debugString = string.Format ("Unit.Start: playerId not set, yielding frame", ""); | |
ClientDebugUI.i.Log (_debugString, GameColor.Magenta); | |
yield return new WaitForEndOfFrame (); | |
} | |
while (!Battle.i.players.ContainsKey(playerId)) | |
{ // *can* happen (observed by Arun 2017.03.29 in 1P-Editor) | |
_debugString = string.Format("Unit.Start: playerId {0} isn't in Battle.players dist, yielding frame", playerId); | |
ClientDebugUI.i.Log (_debugString, GameColor.Magenta); | |
yield return new WaitForEndOfFrame (); | |
} | |
RefreshNetUI (); | |
RefreshParams (); // initial params – may need to be reset if we are 'authorized' later (for SyncVars) | |
// more initialization | |
point = transform.position.ToPoint(); | |
OverrideAnimationController(); | |
skill.Init (); | |
// place under player in the hierarchy | |
transform.SetParent (Battle.i.players [playerId].transform); | |
// register unit in battle unit dict | |
Battle.i.units.Add(netId.Value, this); | |
_debugString = string.Format ("Unit.Start: {0} ready!", netId.Value); | |
Debug.Log (_debugString); | |
ClientDebugUI.i.Log (_debugString, GameColor.Cyan); | |
// add unit id to player unit id list | |
Battle.i.players [playerId].unitIds.Add (netId.Value); | |
_init = true; | |
} | |
[Command] | |
void CmdFwdFieldSetValue(Point point, uint id) | |
{ | |
Field.i.GetTile (point).value = netId.Value; | |
} | |
[Command] | |
public void CmdSetPlayerId(uint id) | |
{ | |
RpcSetPlayerId (id); | |
} | |
[ClientRpc] | |
public void RpcSetPlayerId(uint id) | |
{ | |
_debugString = string.Format ("Unit.RpcSetPlayerId: id={0}", id); | |
Debug.Log (_debugString); | |
ClientDebugUI.i.Log (_debugString, GameColor.Green); | |
this.playerId = id; | |
} | |
// void PlayerIdHook(uint newPlayerId) | |
// { | |
// _debugString = string.Format ("Unit.PlayerIdHook: newPlayerId = {0} ({1})", newPlayerId, hasAuthority); | |
// Debug.Log (_debugString); | |
// ClientDebugUI.i.Log (_debugString, GameColor.Green); | |
// playerId = newPlayerId; | |
// } | |
void ChargeHook(float newCharge) | |
{ | |
newCharge = Mathf.Clamp(newCharge, 0f, (float)speed); | |
if (charge != newCharge) | |
{ | |
charge = newCharge; | |
ChargeChanged.Invoke(charge, (float)speed); | |
isCharged = charge == (float)speed; | |
if(isCharged) | |
{ | |
animator.SetTrigger("Charged"); | |
Charged.Invoke(netId.Value); // static event (for all units!) | |
} | |
} | |
} | |
void OnDestroy() | |
{ | |
if(Battle.i != null && Battle.i.units.ContainsKey(netId.Value)) | |
Battle.i.units.Remove(netId.Value); | |
// clean-up ui | |
if(UIManager.i != null) { | |
foreach (var ui in uis) { | |
UIManager.i.Remove(ui); | |
} | |
} | |
} | |
public Vector3 WorldPosition(FieldObjectSection topMidBot = FieldObjectSection.None) | |
{ | |
if(model == null) | |
{ | |
Debug.LogWarning("UnitBase.WorldPosition: model is null, returning Vector3.zero"); | |
return Vector3.zero; | |
} | |
switch(topMidBot) | |
{ | |
case FieldObjectSection.Top: | |
return model.worldTop; | |
case FieldObjectSection.Bottom: | |
return model.worldBottom; | |
default: | |
return model.worldMiddle; | |
} | |
} | |
void SetHP(int newHP, int direction = 0) | |
{ | |
// _debugString = string.Format ("UnitBase.SetHP: newHP={0}", newHP); | |
// ClientDebugUI.i.Log (_debugString, GameColor.Black); | |
// Debug.Log (_debugString); | |
int oldHP = hp; | |
hp = newHP < hpMax ? newHP : hpMax; // limit hp to max | |
if(hp == oldHP) { | |
_debugString = string.Format("UnitBase.SetHP: new hp same as old hp ({0}), will still invoke HPChanged event", hp); | |
Debug.LogWarning (_debugString); | |
} | |
HpChanged.Invoke(oldHP, hp, direction); | |
if(hp <= 0) | |
{ | |
Died.Invoke(netId.Value); | |
} | |
} | |
public void OverrideAnimationController() | |
{ | |
// use an 'override controller' so we can customize the 'attack state' and clip based on which weapon is equipped | |
// TEMP, HACK: currently the default human anim controller has 'dagger' attack animation in it's attack state, and | |
// we override for 'bow' and 'hand' only. | |
if(weapon == (int)WeaponType.Hand || weapon == (int)WeaponType.Bow) | |
{ | |
string weaponString = ((WeaponType)weapon).ToString().ToLower(); | |
string s = string.Format("{0}_override", weaponString); | |
var overrideController = Util.Instantiate<AnimatorOverrideController>(s); | |
model.SetAnimatorOverrideController(overrideController); | |
// string debugString = string.Format ("Unit.UpdateAnimationOverrideController: overrideing {0}'s animator with {1}", myName, s); | |
// ClientDebugUI.i.Log (debugString); | |
// Debug.Log (debugString); | |
} | |
} | |
void ProcessDeath() | |
{ | |
//string debugString = string.Format ("UnitBase.ProcessDeath:", ""); | |
//ClientDebugUI.i.Log (debugString); | |
//Debug.Log (debugString); | |
hp = 0; | |
ChargeChanged.Invoke(charge, (float)speed); | |
// turn off the charge glow (if it was on) | |
model.HighlightShader(false, HighlightColors.White); | |
} | |
public void SetDirection(GlobalDirection newDiection) | |
{ | |
direction = newDiection; | |
} | |
public void TakeDamage(uint damage, uint damagorId, uint direction) | |
{ | |
//int preHp = this.hp; | |
int postHp = this.hp - (int)damage; | |
SetHP (postHp); | |
// if the unit dies as a result of the action, replace the input callbacks with the death animation | |
// and move the original callbacks to trigger at the end of the death animation | |
Action[] callbacks = null; | |
if (postHp <= 0) | |
{ | |
// process own death (clears status effects, removes any existing animation logic scripts, | |
// removes any left over charged actions from the Battle instance, etc.) | |
ProcessDeath (); | |
callbacks = new Action[] { | |
() => | |
{ | |
SkillAnimLogic deathAnimLogic = model.gameObject.AddComponent<Death>(); | |
deathAnimLogic.Init(); | |
}, | |
}; | |
} | |
// flip direction of damage (perspective of receiving unit) | |
var globalDmgDir = BattleUtil.GetOppositeDirection( (GlobalDirection)direction ); | |
RelativeDirection relativeDir = BattleUtil.GetRelativeDirection(this.direction, globalDmgDir); | |
// HACK: at the same time as we process the damage, trigger the 'take damage' animation of | |
// the target (and pass on the callbacks so that they trigger at the end of that animation) | |
//Debug.Log("b (" + preHp + "," + postHp + ")"); | |
TakeDamage damageAnimLogic = model.gameObject.AddComponent<TakeDamage>(); | |
if (damageAnimLogic != null) { | |
damageAnimLogic.Init (callbacks, null, relativeDir); | |
} | |
} | |
public void RecoverHp(uint health, uint healorId, uint direction) | |
{ | |
this.SetHP(hp + Convert.ToInt32(health)); | |
} | |
[Command] | |
public void CmdDealDamage(uint damage, uint targetId, uint direction) | |
{ | |
var targetNetUnit = (Unit)Battle.i.units [targetId]; | |
targetNetUnit.RpcTakeDamage(damage, netId.Value, direction); | |
} | |
[ClientRpc] | |
public void RpcTakeDamage(uint damage, uint damagorId, uint direction) | |
{ | |
this.TakeDamage(damage, damagorId, direction); // use IUnit interface | |
} | |
void RefreshNetUI() | |
{ | |
NetIdUI netIdUI; | |
var ids = UIManager.i.FindAll<NetIdUI> ().Where<NetIdUI> (n => n.id == netId.Value).ToList(); | |
if (ids != null && ids.Count > 0) { | |
Assert.IsTrue (ids.Count == 1, ids.Count.ToString()); | |
netIdUI = ids [0]; | |
} else { | |
netIdUI = UIManager.i.Add<NetIdUI> ("NetIdUI"); | |
} | |
// _debugString = string.Format ("Unit.AddRefreshNetUI: {0}", ""); | |
// Debug.Log (_debugString); | |
// ClientDebugUI.i.Log (_debugString, GameColor.Black); | |
netIdUI.Init(this, true); | |
netIdUI.SetTextColor(Battle.i.players[playerId].color); | |
netIdUI.transform.localScale = Vector3.one * 0.14f; | |
netIdUI.transform.SetAsLastSibling (); | |
} | |
void RefreshParams() | |
{ | |
UnitParams unitParams = GetComponent<UnitParams> (); | |
hp = unitParams.hp; | |
hpMax = unitParams.hpMax; | |
name = unitParams.myName; | |
charge = unitParams.charge; // NOTE: this is a SyncVar and so is set again in OnAuthorized | |
speed = unitParams.speed; | |
attackPower = unitParams.attackPower; | |
defense = unitParams.defense; | |
moveRange = unitParams.moveRange; | |
jumpRange = unitParams.jumpRange; | |
level = unitParams.level; | |
job = (int)unitParams.job; | |
weapon = (int)unitParams.weapon; | |
} | |
// HACK (http://answers.unity3d.com/questions/1170290/unet-indexoutofrangeexception-networkreaderreadbyt.html) | |
public override void OnDeserialize(NetworkReader reader, bool initialState) | |
{ | |
base.OnDeserialize(reader, initialState); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment