❥ 由于大三期末N个大课设轮番轰炸,停下了手里的好多事。
故时隔一月余久,我又去继续催化RPG小游戏Demo了。
❥ 此次短暂优化之后,基本的战斗系统、对话系统和背包系统已具雏形,
画面渲染也较为惹眼舒适了。
❥ 不知不觉,实习已近一月,在mentor的指导和同事的帮助下,成功接手并完成了一些开发业务单,明天开始为期两周左右的GameJam了,暂且搁置这一Demo探索。
❥ 等新鲜的-科技风-元素塔防出炉之后,再来和大家分享可以试玩的作品。
❥ 先有蛋还是先有鸡?反正先发B站才方便插视频URL hh~
⭐️部分场景展示:
⭐️项目的架构大致如下:
在此次的 Demo 制作中,借用了 Unity Asset Store 的一些免费资源,效果还是不错的
比如下面这个 Free SkyBox,可以呈现一个基础的3D天空场景
其实还是比较 beautiful 的对不对? 这样的对目前来说其实也够用了
将 Materials 中的 Skybox 拖进 Hierarchy 中即可产生效果,主要是Unity的版本要 > 2019.4.0
在初步制作的时候,我们需要在基础之上对一些 Bug 进行纠错 (主要是效果展现上的差距和程序上的不完善),最终不断丰富我们的表现效果。
要考虑的东西有很多:
⭐️比如如何设计角色移动和攻击方式 (在 Unity 客户端中,可以像我一样利用鼠标响应,点击即立刻前往,点击并拖拽光标能朝着光标拖拽的方向即时丝滑移动。当停止移动并在攻击范围之内,即可点击敌人进行攻击。移动Move() 与 攻击Combat() 的细节逻辑处理也是一个重要的东西,是利用了混合树结合代码逻辑解决的);
⭐️比如死亡的对象要进行销毁,使它不再具有物理意义,也要注意不要让死亡的NPC跟随我们的角色移动,避免造成一种混乱的现象。
⭐️比如一个有地势差异的比较大的场景混合各种小场景,如何比较好的处理角色能否移动,这个时候我们就要利用 Bake烘焙 辅助处理,通过控制 Navigation 中 Bake 的属性值来准确控制表现效果,如下图:
NavMesh 与 Bake 具体可以参考下面两篇文章:
Unity | 深入了解NavMeshAgent_米莱虾的博客-CSDN博客_navmeshagent 详解
Unity | Navmesh自动寻路运行报错分析与解决方案_米莱虾的博客-CSDN博客
⭐️比如我们如何将视角绑定在角色身上或者别的想要被绑定的 target 上,这就要用到跟随相机,在 Camera 下挂载 Follow Camera,将 Follow Camera 调整到距离 target 合适的位置上并且与我们的目标绑定(挂载),从而达到一个视角跟随主人公移动的效果,但其实没几行代码...
-
using System.Collections;
-
using System.Collections.Generic;
-
using UnityEngine;
-
-
namespace
RPG.Core
-
{
-
public
class
FollowCamera :
MonoBehaviour
-
{
-
[
SerializeField] Transform target;
-
-
void LateUpdate()
-
{
-
transform.position = target.position;
-
}
-
}
-
}
其他一些具体的细节以及优化有机会再和大家分享,下面呈现部分重要的代码
⭐️Fighter.cs (主要是我们角色战斗逻辑的一些处理)
-
using UnityEngine;
-
using RPG.Movement;
-
using RPG.Core;
-
using GameDevTV.Saving;
-
using RPG.Attributes;
-
using RPG.Stats;
-
using System.Collections.Generic;
-
using GameDevTV.Utils;
-
using System;
-
using GameDevTV.Inventories;
-
-
namespace
RPG.Combat
-
{
-
public
class
Fighter :
MonoBehaviour,
IAction
-
{
-
[
SerializeField]
float timeBetweenAttacks =
1f;
-
[
SerializeField] Transform rightHandTransform =
null;
-
[
SerializeField] Transform leftHandTransform =
null;
-
[
SerializeField] WeaponConfig defaultWeapon =
null;
-
[
SerializeField]
float autoAttackRange =
4f;
-
-
Health target;
-
Equipment equipment;
-
float timeSinceLastAttack = Mathf.Infinity;
-
WeaponConfig currentWeaponConfig;
-
LazyValue<Weapon> currentWeapon;
-
-
private void Awake() {
-
currentWeaponConfig = defaultWeapon;
-
currentWeapon =
new LazyValue<Weapon>(SetupDefaultWeapon);
-
equipment = GetComponent<Equipment>();
-
if (equipment)
-
{
-
equipment.equipmentUpdated += UpdateWeapon;
-
}
-
}
-
-
private Weapon SetupDefaultWeapon()
-
{
-
return AttachWeapon(defaultWeapon);
-
}
-
-
private void Start()
-
{
-
currentWeapon.ForceInit();
-
}
-
-
private void Update()
-
{
-
timeSinceLastAttack += Time.deltaTime;
-
-
if (target ==
null)
return;
-
if (target.IsDead())
-
{
-
target = FindNewTargetInRange();
-
if (target ==
null)
return;
-
}
-
-
if (!GetIsInRange(target.transform))
-
{
-
GetComponent<Mover>().MoveTo(target.transform.position,
1f);
-
}
-
else
-
{
-
GetComponent<Mover>().Cancel();
-
AttackBehaviour();
-
}
-
}
-
-
public void EquipWeapon(WeaponConfig weapon)
-
{
-
currentWeaponConfig = weapon;
-
currentWeapon.
value = AttachWeapon(weapon);
-
}
-
-
private void UpdateWeapon()
-
{
-
var weapon = equipment.GetItemInSlot(EquipLocation.Weapon)
as WeaponConfig;
-
if (weapon ==
null)
-
{
-
EquipWeapon(defaultWeapon);
-
}
-
else
-
{
-
EquipWeapon(weapon);
-
}
-
}
-
-
private Weapon AttachWeapon(WeaponConfig weapon)
-
{
-
Animator animator = GetComponent<Animator>();
-
return weapon.Spawn(rightHandTransform, leftHandTransform, animator);
-
}
-
-
public Health GetTarget()
-
{
-
return target;
-
}
-
-
public Transform GetHandTransform(bool isRightHand)
-
{
-
if (isRightHand)
-
{
-
return rightHandTransform;
-
}
-
else
-
{
-
return leftHandTransform;
-
}
-
}
-
-
private void AttackBehaviour()
-
{
-
transform.LookAt(target.transform);
-
if (timeSinceLastAttack > timeBetweenAttacks)
-
{
-
// This will trigger the Hit() event.
-
TriggerAttack();
-
timeSinceLastAttack =
0;
-
}
-
}
-
-
private Health FindNewTargetInRange()
-
{
-
Health best =
null;
-
float bestDistance = Mathf.Infinity;
-
foreach (
var candidate in FindAllTargetsInRange())
-
{
-
float candidateDistance = Vector3.Distance(
-
transform.position, candidate.transform.position);
-
if (candidateDistance < bestDistance)
-
{
-
best = candidate;
-
bestDistance = candidateDistance;
-
}
-
}
-
return best;
-
}
-
-
private IEnumerable<Health> FindAllTargetsInRange()
-
{
-
RaycastHit[] raycastHits = Physics.SphereCastAll(transform.position,
-
autoAttackRange, Vector3.up);
-
foreach (
var hit
in raycastHits)
-
{
-
Health health = hit.transform.GetComponent<Health>();
-
if (health ==
null)
continue;
-
if (health.IsDead())
continue;
-
if (health.gameObject == gameObject)
continue;
-
yield
return health;
-
}
-
}
-
-
private void TriggerAttack()
-
{
-
GetComponent<Animator>().ResetTrigger(
"stopAttack");
-
GetComponent<Animator>().SetTrigger(
"attack");
-
}
-
-
// Animation Event
-
void Hit()
-
{
-
if(target ==
null) {
return; }
-
-
float damage = GetComponent<BaseStats>().GetStat(Stat.Damage);
-
BaseStats targetBaseStats = target.GetComponent<BaseStats>();
-
if (targetBaseStats !=
null)
-
{
-
float defence = targetBaseStats.GetStat(Stat.Defence);
-
damage /=
1 + defence / damage;
-
}
-
-
if (currentWeapon.
value !=
null)
-
{
-
currentWeapon.
value.OnHit();
-
}
-
-
if (currentWeaponConfig.HasProjectile())
-
{
-
currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, target, gameObject, damage);
-
}
-
else
-
{
-
target.TakeDamage(gameObject, damage);
-
}
-
}
-
-
void Shoot()
-
{
-
Hit();
-
}
-
-
private bool GetIsInRange(Transform targetTransform)
-
{
-
return Vector3.Distance(transform.position, targetTransform.position) < currentWeaponConfig.GetRange();
-
}
-
-
public bool CanAttack(GameObject combatTarget)
-
{
-
if (combatTarget ==
null) {
return
false; }
-
if (!GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) &&
-
!GetIsInRange(combatTarget.transform))
-
{
-
return
false;
-
}
-
Health targetToTest = combatTarget.GetComponent<Health>();
-
return targetToTest !=
null && !targetToTest.IsDead();
-
}
-
-
public void Attack(GameObject combatTarget)
-
{
-
GetComponent<ActionScheduler>().StartAction(
this);
-
target = combatTarget.GetComponent<Health>();
-
}
-
-
public void Cancel()
-
{
-
StopAttack();
-
target =
null;
-
GetComponent<Mover>().Cancel();
-
}
-
-
private void StopAttack()
-
{
-
GetComponent<Animator>().ResetTrigger(
"attack");
-
GetComponent<Animator>().SetTrigger(
"stopAttack");
-
}
-
}
-
}
⭐️PlayerController.cs (主要是我们角色控制逻辑的一些处理,包括角色的自动寻路、和UI的交互、技能、和组件的交互、移动的交互、射线投射...)
-
using RPG.Combat;
-
using RPG.Movement;
-
using UnityEngine;
-
using RPG.Attributes;
-
using System;
-
using UnityEngine.EventSystems;
-
using UnityEngine.AI;
-
using GameDevTV.Inventories;
-
-
namespace
RPG.Control
-
{
-
public
class
PlayerController :
MonoBehaviour
-
{
-
Health health;
-
ActionStore actionStore;
-
-
[
System.Serializable]
-
struct CursorMapping
-
{
-
public CursorType type;
-
public Texture2D texture;
-
public Vector2 hotspot;
-
}
-
-
[
SerializeField] CursorMapping[] cursorMappings =
null;
-
[
SerializeField]
float maxNavMeshProjectionDistance =
1f;
-
[
SerializeField]
float raycastRadius =
1f;
-
[
SerializeField]
int numberOfAbilities =
6;
-
-
bool isDraggingUI =
false;
-
-
private void Awake() {
-
health = GetComponent<Health>();
-
actionStore = GetComponent<ActionStore>();
-
}
-
-
private void Update()
-
{
-
if (InteractWithUI())
return;
-
if (health.IsDead())
-
{
-
SetCursor(CursorType.None);
-
return;
-
}
-
-
UseAbilities();
-
-
if (InteractWithComponent())
return;
-
if (InteractWithMovement())
return;
-
-
SetCursor(CursorType.None);
-
}
-
-
private bool InteractWithUI()
-
{
-
if (Input.GetMouseButtonUp(
0))
-
{
-
isDraggingUI =
false;
-
}
-
if (EventSystem.current.IsPointerOverGameObject())
-
{
-
if (Input.GetMouseButtonDown(
0))
-
{
-
isDraggingUI =
true;
-
}
-
SetCursor(CursorType.UI);
-
return
true;
-
}
-
if (isDraggingUI)
-
{
-
return
true;
-
}
-
return
false;
-
}
-
-
private void UseAbilities()
-
{
-
for (
int i =
0; i < numberOfAbilities; i++)
-
{
-
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
-
{
-
actionStore.Use(i, gameObject);
-
}
-
}
-
}
-
-
private bool InteractWithComponent()
-
{
-
RaycastHit[] hits = RaycastAllSorted();
-
foreach (RaycastHit hit
in hits)
-
{
-
IRaycastable[] raycastables = hit.transform.GetComponents<IRaycastable>();
-
foreach (IRaycastable raycastable
in raycastables)
-
{
-
if (raycastable.HandleRaycast(
this))
-
{
-
SetCursor(raycastable.GetCursorType());
-
return
true;
-
}
-
}
-
}
-
return
false;
-
}
-
-
RaycastHit[] RaycastAllSorted()
-
{
-
RaycastHit[] hits = Physics.SphereCastAll(GetMouseRay(), raycastRadius);
-
float[] distances =
new
float[hits.Length];
-
for (
int i =
0; i < hits.Length; i++)
-
{
-
distances[i] = hits[i].distance;
-
}
-
Array.Sort(distances, hits);
-
return hits;
-
}
-
-
private bool InteractWithMovement()
-
{
-
Vector3 target;
-
bool hasHit = RaycastNavMesh(
out target);
-
if (hasHit)
-
{
-
if (!GetComponent<Mover>().CanMoveTo(target))
return
false;
-
-
if (Input.GetMouseButton(
0))
-
{
-
GetComponent<Mover>().StartMoveAction(target,
1f);
-
}
-
SetCursor(CursorType.Movement);
-
return
true;
-
}
-
return
false;
-
}
-
-
private bool RaycastNavMesh(out Vector3 target)
-
{
-
target =
new Vector3();
-
-
RaycastHit hit;
-
bool hasHit = Physics.Raycast(GetMouseRay(),
out hit);
-
if (!hasHit)
return
false;
-
-
NavMeshHit navMeshHit;
-
bool hasCastToNavMesh = NavMesh.SamplePosition(
-
hit.point,
out navMeshHit, maxNavMeshProjectionDistance, NavMesh.AllAreas);
-
if (!hasCastToNavMesh)
return
false;
-
-
target = navMeshHit.position;
-
-
return
true;
-
}
-
-
private void SetCursor(CursorType type)
-
{
-
CursorMapping mapping = GetCursorMapping(type);
-
Cursor.SetCursor(mapping.texture, mapping.hotspot, CursorMode.Auto);
-
}
-
-
private CursorMapping GetCursorMapping(CursorType type)
-
{
-
foreach (CursorMapping mapping
in cursorMappings)
-
{
-
if (mapping.type == type)
-
{
-
return mapping;
-
}
-
}
-
return cursorMappings[
0];
-
}
-
-
public static Ray GetMouseRay()
-
{
-
return Camera.main.ScreenPointToRay(Input.mousePosition);
-
}
-
}
-
}
转载:https://blog.csdn.net/Luoxiaobaia/article/details/125865286