unity Mirror使用心得一(玩家角色创建,控制,及其攻击其他玩家的血量同步设置)

21 篇文章 1 订阅
17 篇文章 0 订阅

先分享下个人mirrordemo 的github :
https://github.com/IsaWinding/MirrorDemo.git
mirror 的官方下载地址:
https://assetstore.unity.com/packages/tools/network/mirror-129321
1.添加NetworkManager
在这里插入图片描述
2.添加NetworkMangerHud
在这里插入图片描述
3.制作玩家角色预制体并添加mirror 相关组件
在这里插入图片描述
networkTransform 和networkAnimator ClientAuthority 需要勾选
分享玩家操作脚本

using Mirror;
using Prime31;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CharacterInput : NetworkBehaviour
{

	public float moveSpeed = 8f;
	public float jumpSpeed = 20f;
	public float rayDistance = 4f;
	public float atkDistance = 2f;
	public float atk = 10f;
	public float atkOffset = 1f;
	public bool isFaceRight = true;
	public KeyCode moveLeftKey;
	public KeyCode moveRightKey;
	public KeyCode attackKey;
	public KeyCode jumpKey;
	public LayerMask platformMask = 0;
	public LayerMask enemyMask = 0;

	private bool isMove = false;
	private bool isJump = false;
	private bool isOnGround = true;
	private Vector3 moveDelta;
	private bool isAttack = false;

	private bool curIsFaceRight = true;
	private Rigidbody2D body;
	private Vector3 oriScale;
	private NetworkIdentity identity;
	private CharacterAni characterAni;
	private CharacterHp characterHp;
	public int MaxHp = 100;

	[SyncVar(hook = "HpChange")]
	public int curHp = 100;

	public NetworkIdentity Identity { get { return identity; } }
	void Awake() {
		body = this.gameObject.GetComponent<Rigidbody2D>();
		oriScale = this.transform.localScale;
		identity = this.gameObject.GetComponent<NetworkIdentity>();
		characterAni = this.gameObject.GetComponent<CharacterAni>();
		characterHp = this.gameObject.GetComponent<CharacterHp>();
		characterHp.SetHpInfo(curHp, MaxHp);
	}
    private void Start()
    {
		if(identity.isLocalPlayer)
			CameraSmoothFollow.Instance.SetTarget(this.transform);
    }

    private bool isCanReborn = true;
	private void OnDead() {
		if (isCanReborn)
		{
			Invoke("Reborn", 5f);
			isCanReborn = false;
		}	
	}

	[Command]
	private void Reborn()
	{
		curHp = MaxHp;
		SetHpInfo();
		RpcReborn();
	}
	[ClientRpc]
	private void RpcReborn()
	{
		this.transform.localPosition = Vector3.zero;
		isCanReborn = true;
	}
	public bool IsDead()
	{
		return curHp <= 0;
	}

    // the Update loop contains a very simple example of moving the character around and controlling the animation
    private void Update()
	{
		if (!identity.isLocalPlayer)
			return;
		if (IsDead())
			return;
		isMove = false;
		moveDelta = Vector3.zero;
		isOnGround = Physics2D.Raycast(this.transform.position, Vector2.down, rayDistance, platformMask);
		if (isOnGround && Input.GetKeyDown(jumpKey))
		{
			isJump = true;
		}
		if (Input.GetKeyDown(attackKey))
		{
			isAttack = true;
		}
		if (Input.GetKey(moveLeftKey)) {
			isMove = true;
			moveDelta.x = -moveSpeed;
		}
		else if (Input.GetKey(moveRightKey))
		{
			isMove = true;
			moveDelta.x = moveSpeed;
		}
	}
	public void HpChange(int pOld,int hp) {
		characterHp.SetHpInfo(hp, MaxHp);
		if (hp <= 0)
		{
			OnDead();	
		}
	}
	public void SetHpInfo()
	{
		characterHp.SetHpInfo(curHp, MaxHp);
	}
	public void OnDamage(int pDamage)
	{
		curHp -= pDamage;
		if (curHp > MaxHp)
			curHp = MaxHp;
		if (curHp < 0)
			curHp = 0;
		SetHpInfo();
	}


	private bool IsInAtkRange(Transform pTarget)
	{
		var direction = curIsFaceRight ? Vector2.right* atkDistance : -Vector2.right* atkDistance;
		var targetPos = pTarget.position;
		var selfPos = this.transform.position;
		if (Mathf.Abs(targetPos.y - selfPos.y) <= atkOffset && Mathf.Abs(targetPos.x - (selfPos.x + direction.x)) <= atkOffset)
		{
			return true;
		}
		return false;
	}
	[Command]
	public void CmdDoNormalAttack()
	{
		//Debug.LogError("CmdDoNormalAttack");
		var direction = curIsFaceRight ? Vector2.right : -Vector2.right;
		var identitys = GameObject.FindObjectsOfType<CharacterInput>(false);
		for (var i = 0; i < identitys.Length; i++)
		{ 
			if(identitys[i] != this)
			{
				if (IsInAtkRange(identitys[i].transform))
				{
					identitys[i].OnDamage((int)atk);
				}
			}
		}
		var ai = GameObject.FindObjectsOfType<MonsterAI>(false);
		for (var i = 0; i < ai.Length; i++)
		{
			if (ai[i] != this)
			{
				if (IsInAtkRange(ai[i].transform))
				{
					ai[i].OnDamage((int)atk);
				}
			}
		}
	}
	[Command]
	void CmdSetCurFaceRight(bool pSetRight)
	{
		curIsFaceRight = pSetRight;
	}
	private void FixedUpdate()
	{
		if (!identity.isLocalPlayer)
			return;
		if (IsDead())
			return;
		if (isAttack)
        {
			characterAni.PlayAni("attack",4,()=> {
				isAttack = false;
				CmdDoNormalAttack();
			});
		}
		if (isMove)
		{
			this.transform.localPosition += moveDelta * Time.deltaTime;
			int xS = isFaceRight == moveDelta.x > 0 ? 1 : -1;
			curIsFaceRight = xS == 1;
			CmdSetCurFaceRight(curIsFaceRight);
			this.transform.localScale = new Vector3(oriScale.x * xS, oriScale.y, oriScale.z);
			characterAni.PlayAni("move", 3);
		}
		if (isJump)
		{
			body.AddForce(new Vector2(0,jumpSpeed));
			isJump = false;
		}
		if (!isAttack && !isMove)
		{
			characterAni.PlayAni("idle", 1);
		}
	}
}

注意玩家进行攻击其他玩家的操作需要在服务器端运行,下面这段代码需要加上Command,加上Command的话是客户端在服务器段的玩家主体会运行该段逻辑,服务器改变后的血量信息通过
[SyncVar(hook = “HpChange”)]
public void HpChange(int pOld,int hp) {
characterHp.SetHpInfo(hp, MaxHp);
if (hp <= 0)
{
OnDead();
}
}
同步的方法在每个客户端同步血量信息

[Command]
	public void CmdDoNormalAttack()
	{
		//Debug.LogError("CmdDoNormalAttack");
		var direction = curIsFaceRight ? Vector2.right : -Vector2.right;
		var identitys = GameObject.FindObjectsOfType<CharacterInput>(false);
		for (var i = 0; i < identitys.Length; i++)
		{ 
			if(identitys[i] != this)
			{
				if (IsInAtkRange(identitys[i].transform))
				{
					identitys[i].OnDamage((int)atk);
				}
			}
		}
		var ai = GameObject.FindObjectsOfType<MonsterAI>(false);
		for (var i = 0; i < ai.Length; i++)
		{
			if (ai[i] != this)
			{
				if (IsInAtkRange(ai[i].transform))
				{
					ai[i].OnDamage((int)atk);
				}
			}
		}
	}

角色动画播放添加了一个攻击动画播放完的事件用来驱动攻击伤害计算逻辑

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterAni : MonoBehaviour
{
    private Animator animator;
    private string curAniName;
    private int curAniProx = 0;
    private void Awake()
    {
        animator = this.gameObject.GetComponent<Animator>();
    }
    private System.Action onFinish;
    public bool PlayAni(string pAniName,int pProx,System.Action pOnFinish = null)
    {
        if (curAniName != null && IsPlayAning(curAniName) && pProx <= curAniProx)
            return false;
        animator.CrossFade(pAniName,0);
        curAniName = pAniName;
        curAniProx = pProx;
        onFinish = pOnFinish;
        return true;
    }
    private bool IsPlayAning(string pAniName)
    {
        AnimatorStateInfo animatorInfo = animator.GetCurrentAnimatorStateInfo(0);
        if ((animatorInfo.normalizedTime <= 1.0f) && (animatorInfo.normalizedTime > 0f) && (animatorInfo.IsName(pAniName)))
        {
            return true;
        }
        return false;
    }
    public void OnAttackFinish()
    {
        if (onFinish != null)
            onFinish.Invoke();
    }
}

血条显示设置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterHp : MonoBehaviour
{
    public SpriteRenderer hpBg;
    public SpriteRenderer hp;
    public int maxHp = 100;
    public float curHp = 100f;
    private float oriScale = 8;
    void Awake()
    {
        oriScale = hpBg.transform.localScale.x;
    }
    public void SetHpInfo(int pCurHp,int pMaxHp)
    {
        curHp = pCurHp;
        maxHp = pMaxHp;
        SetCurHp();
    }
    private void SetCurHp()
    {
        var hpProgress = curHp / maxHp;
        hp.transform.localScale = new Vector3(hpProgress * oriScale,1,1);
        hp.transform.localPosition = new Vector3(-(1- hpProgress) * oriScale/2, 0,0);
    }
}

一下篇介绍服务器怪物的运行和创建

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值