1. 游戏说明
大乱斗是一种常见的游戏模式,所有角色会进人同一个场景,玩家可以控制它们移动, 也可以让角色攻击敌
- 打开客户端即视为进入游戏,在随机出生点刷出角色
- 使用鼠标左键点击场景,角色会自动走到指定位置
- 在站立状态下,点击鼠标右键可使角色发起攻击,角色会向鼠标指向的方向进行攻击
- 每个角色默认有100滴血(hp),受到攻击会掉血,死亡后从场景消失,提示"game over"
- 若玩家掉线,视为死亡,从场景中消失
2. 类结构设计
大乱斗游戏的核心要素之一是玩家所控制的角色,它可以行走,还可以攻击其他角色。 玩 家 可以 操 控 一 个 角 色 ,
又能够看到其他玩家操控的角 色 , 可想而知 , 这 两种 角 色 应 有 不 同 的 表 现 。 玩 家 操 控 的 角 色 是 由 玩家 驱 动 的 ( 下 称 “ 操 控 角 色 ” ), 它 接 受 鼠 标 的控 制 ; 其 他 玩 家 操 控的 角色 (下 称 “ 同步角 色” ) 是 由网 络 数 据 驱 动的 , 由 服 务端 转 发 角 色 的状 态信 息。 这两 种 角色 有 很 多共 同 点,
比 如 都 可以 行 走、 都 可以 表 现攻 击动 作 等。可以设计图3-8所 示的 类结构,其基类BaseHuman是基础的角色类,它处理“ 操控角 色〞和“同步角色”
的一些共有功能;CtrlHluman类代表“ 操控角色”,它在BaseHluman类的基础上处理鼠标操控功能;SyncHuman
类是“同步角色”类, 它 也 继 承 自 BaseHuman , 并处理网络同步(如果有必要)。
2.1 BaseHuman类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseHuman : MonoBehaviour {
//是否正在移动
internal bool isMoving = false;
//移动目标点
private Vector3 targetPosition;
//移动速度
public float speed = 1.2f;
//动画组件
private Animator animator;
//是否正在攻击
internal bool isAttacking = false;
internal float attackTime = float.MinValue; //最近攻击时间
//描述
public string desc = "";
// Use this for initialization
internal void Start () {
animator = GetComponent<Animator>();
}
// Update is called once per frame
internal void Update () {
MoveUpdate();
AttackUpdate();
}
//移动Update
public void MoveUpdate(){
//判断当前是否处于行走状态,如果不是行走状态,就不须往目的地行进了
if(isMoving == false) {
return;
}
Vector3 pos = transform.position;
//MoveTowards的作用是计算pos朝targetPosition方向移动一段距离后的位置
transform.position = Vector3.MoveTowards(pos, targetPosition, speed*Time.deltaTime);
//让角色
transform.LookAt(targetPosition);
if(Vector3.Distance(pos, targetPosition) < 0.05f){
isMoving = false;
animator.SetBool("isMoving", false);
}
}
//移动到某处
public void MoveTo(Vector3 pos) {
targetPosition = pos; //把目的地坐标赋给targetPosition
isMoving = true; //正在移动
animator.SetBool("isMoving", true);
}
//攻击动作
public void Attack(){
isAttacking = true;
attackTime = Time.time;
animator.SetBool("isAttacking", true);
}
//攻击Update
public void AttackUpdate(){
if(!isAttacking) return;
if(Time.time - attackTime < 1.2f) return;
isAttacking = false;
animator.SetBool("isAttacking", false);
}
}
(1) 继承关系
BaseHuman类继承自MonoBehaviour,说明它可以作为组件挂到Gameobject身上,也说明它拥有MonoBehaviour的一些性质,比如在唤醒时会执行Awake和Start,每帧会执行一次Update。
可见继承MonoBehaviour会有Start和Update方法
(2) 移动功能
- isMoving是bool型变量,指代角色是否正在移动
- targetPosition是Vector3类型的坐标,代表角色移动的目的地。
- MoveTo方法会把目的地的坐标赋给targetPosition,该方法给玩家指定目的地,当玩家点击鼠标时,会调用所控制角色的MoveTo方法,设置目的地坐标targetPosition和动画状态
- MoveUpdate会让玩家一步步往目的地方向移动,它会每帧执行一次。
- speed代表移动的速
- 使用transform.LookAt让角色转向目标点
- MoveUpdat e 是 一 个 被 Update调用的方 法, 所以它会每帧执行一次。 首先通 过 if (isMoving== false)判断当前是否处于行走状态,如果不是行走状态,就无须往目的地行进了; 接着通过Vector3.MoveTowards 计算新位置 ,MoveTowards的作用 是计算pos 朝 targetPosition 方向移动一段距离后的位置; 之后使用transform.Look At让角色转向目标点; 最后使用Vector3.Distance判断当前位置与目标位置的距离, 如果距离足够小,就认为角色到达了目的地,再将角色状态更改为站立(isMoving=false).
(3)动画功能
BaseHuman中定义了Animator型变量animator,它会指代角色身上的动画控制器。动画控制器中设有isMoving参数,程序通过类似"animator.SetBool(“isMoving”, false);"的语句改变动画控制器的值,从而让角色播放行走和站立两种动画。
2.2 CtrlHuman 操作角色
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CtrlHuman : BaseHuman {
// Use this for initialization
new void Start () {
base.Start();
}
// Update is called once per frame
new void Update () {
base.Update();
//移动 判断鼠标是否被按下:参数为0表示判断鼠标左键是否被按下
//Input.mousePosition代表当前鼠标所指的屏幕坐标
if(Input.GetMouseButtonDown(0)){
// 发出一条射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
Physics.Raycast(ray, out hit);
//射线与场景发生碰撞(Tag为Terrain) 那么碰撞点就是角色移动的目标点
if (hit.collider.tag == "Terrain"){
MoveTo(hit.point);
//发送协议
string sendStr = "Move|";
sendStr += NetManager.GetDesc()+ ",";
sendStr += hit.point.x + ",";
sendStr += hit.point.y + ",";
sendStr += hit.point.z + ",";
NetManager.Send(sendStr);
}
}
//攻击 判断鼠标右键是否被按下
if(Input.GetMouseButtonDown(1)){
if(isAttacking) return;
if(isMoving) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
Physics.Raycast(ray, out hit);
if(hit.collider.tag != "Terrain") return;
transform.LookAt(hit.point);
Attack();
//发送协议
string sendStr = "Attack|";
sendStr += NetManager.GetDesc()+ ",";
sendStr += transform.eulerAngles.y + ",";
NetManager.Send(sendStr);
//攻击判定
Vector3 lineEnd = transform.position + 0.5f*Vector3.up;
Vector3 lineStart = lineEnd + 20*transform.forward;
if(Physics.Linecast(lineStart, lineEnd, out hit)){
GameObject hitObj = hit.collider.gameObject;
if(hitObj == gameObject)
return;
SyncHuman h = hitObj.GetComponent<SyncHuman>();
if(h == null)
return;
sendStr = "Hit|";
sendStr += NetManager.GetDesc()+ ",";
sendStr += h.desc + ",";
NetManager.Send(sendStr);
}
}
}
}
代码说明:
(1) base.xxx, new void xxx()
base 指当前类的父类,可调用父类的非私有属性和方法,代码中的base.Start 和base. Update
指代调用父类BaseHluman的Start 和Update 方法。 new用作修饰符时,new
关键字可以显式地隐藏从基类继承的成员。隐藏继承的成员 时,该成员的派生版本将替换基类版本。虽然可以在不使用new修饰符的情况下隐藏成员,
但会生成警告。
(2)判断鼠标输入
Input.GetMouseButtonDown(0)用 于判断鼠标是否被按 下:参数为0 表示判断鼠标左键 是否被按下,
参数为1表示判断鼠标右键是否被按下。 而 Input.mousePosition代表当前鼠标所指的屏幕坐标。
(3)获取点击位置
Unity3D中的Camera.ScrenPointToRay 方法能够将屏幕位置转成一条射线,只需填入屏幕坐标点 ,
该方法将返回对应的射线。 射线是在三维坐标中一个点向一 个 方 向 发 射 的 一 条线,Unity3D中可以使用Ray
和Physics.Raycast 来做射线检测。当射线射向碰撞器时 Raycast 返回true, 否则为fal se, 并且可以通过out
hi t 变量获取碰撞点。如果射线与场景发 生碰撞(Tag为Terrain),那么碰撞点就是角色移动的目标点。
2.3 syncHuman同步角色
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CtrlHuman : BaseHuman {
// Use this for initialization
new void Start () {
base.Start();
}
// Update is called once per frame
new void Update () {
base.Update();
//移动
if(Input.GetMouseButtonDown(0)){
Ray ray = Camera.main