ARPG简单游戏原型
主要制作以下三个系统:
移动系统
动画系统
碰撞系统
一、移动系统
这里采用第三人称视角的角色移动方式,角色通过Character controller角色控制器进行移动,摄像机随角色转向一同转向,同时可以进行缩放操作
input.cs
输入控制器采用单例模式,与角色控制器脚本进行分离,让角色控制器可以通过这里的参数直接进行条件判断
public class InputC : MonoBehaviour
{
private static InputC instance;
public Vector3 m_Movement; //存储x,z移动
public bool m_atkTrigger;
public Vector3 m_CamRot; //存储摄像机参数
public static InputC GetInstance()
{
return instance;
}
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
// Update is called once per frame
void Update()
{
m_Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
m_CamRot = new Vector3(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"),Input.GetAxis("Mouse ScrollWheel")); //x,y存储鼠标X轴和Y轴的移动,z存储滑轮滚动
//m_atkTrigger = Input.GetMouseButtonDown(0);
}
}
Player.cs
public class Player : MonoBehaviour
{
CharacterController cc;
Animator anim;
public float moveSpeed = 5;
public bool isAtking;
// Start is called before the first frame update
void Start()
{
cc = GetComponent<CharacterController>();
anim = GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
Move();
Atk();
}
void Move()
{
anim.SetFloat("xSpeed", InputC.GetInstance().m_Movement.x); //通过单例的InputC访问到移动参数
anim.SetFloat("zSpeed", InputC.GetInstance().m_Movement.z);
Vector3 dir = transform.TransformDirection(InputC.GetInstance().m_Movement); //从局部坐标转换到世界坐标 因为SimpleMove需要世界坐标
cc.SimpleMove(dir* moveSpeed*Time.deltaTime*200);
}
/*
void Atk()
{
if (InputC.GetInstance().m_atkTrigger && !isAtking)
{
anim.SetTrigger("attack");
}
}
*/
}
CameraC.cs
public class CameraC : MonoBehaviour
{
public Transform target;
public Vector3 offset;
public float distance; //摄像机缩放距离
// Start is called before the first frame update
public float x, y;//用于接收鼠标滑动
public float rotSpeedX=100; //x鼠标旋转速度
public float rotSpeedY=100; //y旋转速度
public float zoomSpeed =100; //滚轮控制的相机缩放速度
public float yLimitMin=-10, yLimitMax=70; //上下看时的视角限制
public float disMin = 2, disMax = 10; //滚轮缩放限制
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
//offset = target.position - transform.position;
}
//摄像机通常写在LateUpdate 防止一些抖动
private void LateUpdate()
{
x += InputC.GetInstance().m_CamRot.x * rotSpeedX * Time.deltaTime; //获取InputC存储的相机参数
y -= InputC.GetInstance().m_CamRot.y * rotSpeedY * Time.deltaTime;
y = clampAngle(y, yLimitMin, yLimitMax);
transform.rotation = Quaternion.Euler(y, x, 0); //注意这里顺序是y,x 因为鼠标的左右移动实际对应的是y轴的旋转 因此这里的x在y的位置
//transform.eulerAngles = new Vector3(y, x, 0); //第二种写法
if (InputC.GetInstance().m_Movement.x != 0 || InputC.GetInstance().m_Movement.z != 0)
target.rotation = Quaternion.Euler(0, x, 0); //相机旋转的同时将角色也旋转 保证角色y轴旋转方向和相机的y轴旋转方向一致
//设置缩放距离
distance -= (InputC.GetInstance().m_CamRot.z * Time.deltaTime) * zoomSpeed * Mathf.Abs(distance);
//乘上一个distance绝对值让缩放效果更好 有一个渐快渐慢的效果
distance = clampAngle(distance, disMin, disMax);
//更新相机位置为 角色位置+offset偏移量(是否添加偏移可以自己设置) + 相机的旋转角度的向量值乘上距离(相当于将相机往旋转角度方向后移)
transform.position = target.position + offset + transform.rotation * (new Vector3(0, 0, -1)) * distance;
}
//限制角度
float clampAngle(float angle,float min,float max)
{
if (angle > 360) angle -= 360;
else if (angle < -360) angle += 360;
return Mathf.Clamp(angle, min, max);
}
}
二、动画系统
动画系统的脚本代码主要来自于Animator的一个转换条件的设置。我们直接在Player中添加响应的转换条件代码设置就可以
其中移动动画的判断需要x、z方向的速度两个参数
攻击动画 需要一个attack的trigger
参数设置如下:
状态机面板连接如下:
(就提一下idle->移动混合树的 转换条件,像这样四个或的判断)
修改InputC.cs
void Update()
{
m_Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
m_CamRot = new Vector3(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"),Input.GetAxis("Mouse ScrollWheel")); //x,y存储鼠标X轴和Y轴的移动,z存储滑轮滚动
m_atkTrigger = Input.GetMouseButtonDown(0); //用于判断是否点击鼠标左键 注意这里要有Down!!!
}
修改Player.cs,添加Atk攻击判断脚本
void Move()
{
anim.SetFloat("xSpeed", InputC.GetInstance().m_Movement.x); //通过单例的InputC访问到移动参数
anim.SetFloat("zSpeed", InputC.GetInstance().m_Movement.z);
Vector3 dir = transform.TransformDirection(InputC.GetInstance().m_Movement); //从局部坐标转换到世界坐标 因为SimpleMove需要世界坐标
cc.SimpleMove(dir* moveSpeed*Time.deltaTime*200);
}
void Atk()
{
if (InputC.GetInstance().m_atkTrigger && !isAtking)
{
anim.SetTrigger("attack");
}
}
三、碰撞系统
这个原型的一个主要碰撞检测来自于我们的武器和敌人之间的碰撞,当使用onTriggerEnter的时候,两个物体都能访问,为了提高扩展性,我们是选择在敌人对象这边施加伤害的数据。同时提供了一个接口给敌人用于传递受伤的参数。
同时为了角色武器不会在任何时候都能发生碰撞,我们通过在动画中添加事件的方式来进行碰撞的开关
WeaponC.cs 挂载在武器对象上
public class weaponC : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
Debug.Log("hit");
Ifight fi = other.GetComponent<Ifight>(); //通过获取敌人的接口来判断是否能施加伤害
if (fi!=null)
{
fi.beHit(10); //调用敌人身上的beHit函数
Debug.Log("hit");
}
}
}
武器的一些属性可以写在这里 如攻击力,属性效果(冰冻、火焰等等)
MonsterC.cs
public class MonsterC : MonoBehaviour,Ifight //继承接口类,接口需要写在父类后面
{
public float HP = 30;
public void beHit(int damage) //beHit是写在接口的函数
{
Debug.Log(name + "HP-" + damage);
HP -= damage;
if (HP <= 0)
{
Destroy(this.gameObject);
}
}
}
Ifight.cs 接口类
public interface Ifight
{
public void beHit(int damage);
}
EventC.cs 动画事件响应写在这里
public class EventC : MonoBehaviour
{
public Collider weaponColl;
public Player player;
//开启碰撞
public void AtkEnable()
{
weaponColl.enabled = true;
}
//关闭盘碰撞
public void AtkDisable()
{
weaponColl.enabled = false;
}
//攻击开始判断 防止多次点击攻击触发
public void AtkStart()
{
player.isAtking = true;
}
public void AtkEnd()
{
player.isAtking = false;
}
}
在Animation窗口中添加动画事件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了