我有一个问题,当我在穿过 NPC 或移动时单击对话时,角色将继续朝操纵杆的方向移动,然后再将其设置为活动为 false。
我尝试将水平和垂直输入设置为零,甚至将计算的水平和垂直输入的移动方向设置为零,但角色仍然沿上次移动的方向移动。我不确定我创建的 if 语句是否正确,但这就是我想到的想法。
这是玩家移动的脚本:
using UnityEngine;
using UnityEngine.EventSystems;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private GameObject interactButton;
public float speed, rotationSpeed, ySpeed, originalStepOffset;
public Joystick joystick;
private Animator animator;
private CharacterController characterController;
void Start()
{
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
originalStepOffset = characterController.stepOffset;
}
// Update is called once per frame
void FixedUpdate()
{
float horizontalInput = joystick.Horizontal;
float verticalInput = joystick.Vertical;
Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
movementDirection = Quaternion.Euler(0, 45f, 0) * movementDirection;
float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
movementDirection.Normalize();
ySpeed += Physics.gravity.y * Time.deltaTime;
if(characterController.isGrounded)
{
characterController.stepOffset = originalStepOffset;
ySpeed = -0.5f;
}
else
{
characterController.stepOffset = 0;
}
Vector3 velocity = movementDirection * magnitude;
velocity = AdjustVelocityToSlope(velocity);
velocity.y += ySpeed;
//transform.Translate(movementDirection * speed * Time.deltaTime, Space.World);
characterController.Move(velocity * Time.deltaTime);
if (movementDirection != Vector3.zero)
{
if(EventSystem.current.currentSelectedGameObject != null)
{
if (EventSystem.current.currentSelectedGameObject.name == "InteractButton")
{
horizontalInput = 0f;
verticalInput = 0f;
movementDirection = Vector3.zero;
animator.SetBool("IsMoving", false);
}
else
{
//transform.forward = movementDirection;
animator.SetBool("IsMoving", true);
Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
}
}else
{
animator.SetBool("IsMoving", true);
Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
}
} else
{
animator.SetBool("IsMoving", false);
}
}
private Vector3 AdjustVelocityToSlope(Vector3 velocity)
{
var ray = new Ray(transform.position, Vector3.down);
if(Physics.Raycast(ray, out RaycastHit hitInfo, 0.2f))
{
var slopeRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
var adjustedVelocity = slopeRotation * velocity;
if(adjustedVelocity.y < 0)
{
return adjustedVelocity;
}
}
return velocity;
}
}
将按钮设置为 active 为 true 并添加其 onclick 侦听器的函数是一个触发器进入函数和一个触发器退出函数,用于将其设置为 active 为 false 并删除 onclick 侦听器。它附着在多个 NPC 上,每当我靠近它们时,按钮就会出现。如果有人需要在此处查看按钮的脚本,但我只会粘贴到相关部分:
using System;
using System.Collections;
using System.Drawing;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[System.Serializable]
public class DialogueManager : MonoBehaviour
{
/*
// NPC INTERACTION
private static DialogueManager buttonOwner;
private Camera mainCamera;
*/
// NPC DATA
private Character characterJson;
// FOR DIALOG FLOW
private int multiDialogCycle;
public static int dialogId;
// GAME OBJECTS REFERENCE
[SerializeField] private GameObject dialogBox, addPanel, m1, id, darkPanel;
[SerializeField] private Button nameBtn, choiceOneBtn, choiceTwoBtn;
[SerializeField] private TextMeshProUGUI npcName, choiceOne, choiceTwo, dialogName, dialogMessage, addedText;
[SerializeField] private TextAsset characterData;
// FOR MULTIPLE DIALOGUES WITHOUT CHOICES
private bool clickEnable;
private string[] multiDialog;
// CHOICES STORAGE
private static Choice[] choices = new Choice[2];
//private TryInstantiate tryInstantiate = new TryInstantiate();
private GameData gameData;
private void Start()
{
// LOAD NPC DATA
characterJson = JsonUtility.FromJson<Character>(characterData.text);
// SET DEFAULT VALUE
//dialogId = 0;
multiDialogCycle = 0;
clickEnable = false;
// SET SCRIPTABLE OBJECT FOR DATA STORAGE
gameData = Resources.Load<GameData>("GameData/GameData");
}
private void Update()
{
if(nameBtn.gameObject.activeInHierarchy)
{
if(Input.GetKey(KeyCode.Space))
{
EventSystem.current.SetSelectedGameObject(nameBtn.gameObject);
}
}
// FOR ENDING DIALOGUES WITHOUT CHOICES
if (Input.GetMouseButtonDown(0) && clickEnable == true)
{
if (multiDialog.Length > 1 )
{
if(getDialog(dialogId).choices.Length == 0 && multiDialogCycle == multiDialog.Length - 1)
{
addArticles();
addObjectives();
addClues();
closeDialog();
Debug.Log(getDialog(dialogId).minigame);
}
else
{
if(getDialog(dialogId).minigame != "" && multiDialogCycle < multiDialog.Length - 1)
{
if(!gameData.idShown)
{
darkPanel.SetActive(true);
id.SetActive(true);
}
}else
{
multiDialogCycle++;
loadCharacterData();
}
}
}
else
{
if (getDialog(dialogId).minigame != "")
{
if(getDialog(dialogId).minigame == "spot_object")
{
m1.SetActive(true);
}
dialogBox.SetActive(false);
gameData.dialogActive = false;
}
else
{
addArticles();
addObjectives();
addClues();
closeDialog();
}
}
}
if(gameData.idShown)
{
multiDialogCycle++;
loadCharacterData();
gameData.idShown = false;
}
if (gameData.dialogActive && dialogId != 0 && getDialog(dialogId).minigame != "" && !gameData.loadedData)
{
updateID();
loadCharacterData();
}
// FOR NPC NAMES
/*
if (buttonOwner != this) return;
if (!mainCamera) mainCamera = Camera.main;
var position = mainCamera.WorldToScreenPoint(head.position + offset);
//uiUse.transform.position = position;
nameBtn.transform.position = position;
*/
}
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.gameObject.SetActive(true);
nameBtn.GetComponent<Image>().sprite = Resources.Load<Sprite>("InteractionAsset/DIALOGUE");
nameBtn.transform.GetChild(0).GetComponent<TextMeshProUGUI>().color = new Color32(75,75,75,255);
//buttonOwner = this;
nameBtn.onClick.RemoveListener(onNameClick);
nameBtn.onClick.AddListener(onNameClick);
choiceOneBtn.onClick.RemoveListener(onChoiceClick);
choiceOneBtn.onClick.AddListener(onChoiceClick);
choiceTwoBtn.onClick.RemoveListener(onChoiceClick);
choiceTwoBtn.onClick.AddListener(onChoiceClick);
npcName.text = characterJson.name;
}
}
private void OnTriggerExit(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.onClick.RemoveListener(onNameClick);
choiceOneBtn.onClick.RemoveListener(onChoiceClick);
choiceTwoBtn.onClick.RemoveListener(onChoiceClick);
nameBtn.gameObject.SetActive(false);
//buttonOwner = null;
}
}
// DIALOGUE SYSTEM
public void onNameClick()
{
nameBtn.gameObject.SetActive(false);
dialogBox.SetActive(true);
gameData.dialogActive = true;
FindObjectOfType<AudioManager>().Play("ButtonSound");
if (dialogBox.activeInHierarchy)
{
if (dialogMessage != null && dialogName != null)
{
loadCharacterData();
interactedNPC();
}
else
{
// Debug.Log("null dialog message");
}
}
}
public void updateID()
{
if (gameData.win && !gameData.loadedData)
{
dialogId = gameData.targetId_1;
gameData.loadedData = true;
}
else if (!gameData.win && !gameData.loadedData)
{
dialogId = gameData.targetId_2;
gameData.loadedData = true;
}
}
我怎样才能至少重置操纵杆的位置。我目前正在使用 Unity Asset Store 中的操纵杆包。
我将从对话步骤开始对话流程。对话启动后,您可以设置一个布尔值或任何其他类型的属性(甚至是引用的对象,例如对话本身或与之聊天的 NPC,但布尔值更简单)作为标记。简单来说:
public bool IsTalking;
。这可能位于 PlayerMovement 或主要的类似 Player 的组件(或 SO)中,您可以通过 GameObject 或不同的公共变量从 PlayerMovement 访问它们。
接下来,一旦对话机制启动,您可以将Player-like组件或PlayerMovement中的标记(例如bool或NPC ref)设置为true,当聊天停止时为false。
然后在 PlayerMovement.FixedUpdate() 中你可以停止代码执行:
// Update is called once per frame
void FixedUpdate()
{
// or if (Player.IsTalking == true) if you want the marker there,
// but you can have it inside PlayerMovement if you wish as long as
// you can reference it without overhead - meaning don't do crazy
// stuff like searching all game objects/components for PlayerMovement)
if (IsTalking == true)
{
if (characterController != null)
characterController.Move(Vector3.zero);
if (animator != null)
animator.SetBool("IsMoving", false);
return;
}
// everything starts executing once the player stops talking to NPCs
float horizontalInput = joystick.Horizontal;
float verticalInput = joystick.Vertical;
Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
movementDirection = Quaternion.Euler(0, 45f, 0) * movementDirection;
float magnitude = Mathf.Clamp01(movementDirection.
据我了解,对话窗口是模态的。根据您的需要,您可以使用
停止时间进程的内部增量Time.timeScale = 0f;
但这会停止,例如(UI)动画和事件系统的正确功能,所以有些人使用
Time.timeScale = 0.0001f;
这有一个缺点,即游戏玩法继续缓慢,当用户在暂停屏幕中休息时,我们的玩家英雄可能会被非常慢的火箭击中。
我想补充一点,您只需调用 Player 脚本上的一个方法来确定该布尔值设置为 true 还是 false,而不是公开内部变量。像这样的东西:
private bool canMove = true;
[YarnCommand("canMove")] // you don't need this if not using YarnSpinner
public void CanMove(bool permission) {
canMove = permission;
}
然后,无论采用哪种方法处理您的运动,请务必输入以下内容:
private void MoveCharacter(Vector2 direction) {
if (!canMove) {
return;
}
//... rest of move code
这样做的原因是您不希望其他脚本能够直接修改您的属性。最好公开一个只做单一事情的公共方法。
稍后,如果您决定需要更多逻辑,那么唯一需要更改的是
CanMove
方法,其他一切保持不变。
请注意,时髦的
[YarnCommand()]
条目仅适用于 YarnSpinner,如果您不使用 YarnSpinner,则不需要它。这只是 Yarn 脚本能够从对话中调用 C# 方法的一种方式。
奖金: 为了从另一个脚本中调用此方法,您需要首先访问它,使用
FindObjectOfType<>
:
private Move moveScript; // Move is the script's class name
private void Start() {
moveScript = FindObjectOfType<Move>();
}
然后当你想在对话开始后调用该方法时:
moveScript.CanMove(false);
当你的对话结束时:
moveScript.CanMove(true);