This page has been translated automatically.
UNIGINE 基础课程
1. 简介
2. 虚拟世界管理
3. 3D模型准备
4. 材质
5. 摄像机和光照系统
6. 实现应用程序逻辑
7. 制作过场动画与动画序列
8. 准备发布项目
9. 物理系统
10. 优化基础
11. 项目2:第一人称射击游戏
13. 项目4:带有简单交互的 VR 应用程序

实现控制逻辑

Let's create another component named InputController that implements keyboard event handling. We'll make it universal to ensure simple extensibility, for example, it will be easy to add processing of input events from joysticks and other devices.现在我们来创建另一个组件,命名为InputController,用于处理键盘事件。我们将其设计为通用结构,方便将来扩展,比如添加对手柄或其他设备的支持。

Let's define a list of events that can occur and their corresponding keys:下面是我们定义的可触发事件及其对应的按键:

  • Hitting the gas (W)油门(W
  • Brake (S)刹车(S
  • Turn (A and D)转向(AD
  • Hand brake (Spacebar)手刹(空格键
  • Position reset (F5)位置重置(F5
InputController.h
#pragma once
// 引入组件系统头文件
#include <UnigineComponentSystem.h>
#include <UnigineInput.h>
#include <UniginePhysics.h>

class InputController : public Unigine::ComponentBase
{
private:
	static InputController* instance;

public:
	// 组件构造函数和方法列表
	COMPONENT_DEFINE(InputController, ComponentBase);
	// -------------------------------
	COMPONENT_INIT(init, 0);
	COMPONENT_UPDATE(update,0);

	// 定义操作类型列表:油门、刹车、转向、手刹和位置重置
	enum INPUT_ACTION_TYPE
	{
		THROTTLE = 0,
		BRAKE,
		WHEEL_LEFT,
		WHEEL_RIGHT,
		HAND_BRAKE,
		RESET,
	};

	static float getAction(InputController::INPUT_ACTION_TYPE action);
	static InputController* getInstance();

	// 输入状态接口
	class IInputState
	{
		public:
			virtual ~IInputState() {}
			virtual float getState() { return 0; }
	};

	// 按键按下 和 长按 的接口实现
	class InputStateKeyboardPressed : public IInputState {
		private:
			Unigine::Input::KEY key = Unigine::Input::KEY::KEY_UNKNOWN;

		public:
			InputStateKeyboardPressed(Unigine::Input::KEY key) { this->key = key; }
			float getState()  {
				return Unigine::Input::isKeyPressed(key) ? 1.0f : 0.0f;
			};
	};

	class InputStateKeyboardDown : public IInputState {

		private:
			Unigine::Input::KEY key = Unigine::Input::KEY::KEY_UNKNOWN;

		public:
			InputStateKeyboardDown(Unigine::Input::KEY key) { this->key = key; }
			float getState() {
				return Unigine::Input::isKeyDown(key) ? 1.0f : 0.0f;
			};
	};

	// 输入行为的抽象类,在初始化时接受一组状态列表;
	// 如果其中至少有一个状态被触发,则其状态值为 1.0f
	private: class InputAction
	{
		private: 
			Unigine::Vector <IInputState*> states;

		public:
			float state = 0.0f;

			InputAction(Unigine::Vector <IInputState*> states) { this->states = states; }

			void update()
			{
				float s = -Unigine::Math::Consts::INF; 

				for(int i=0; i < states.size();i++)
				{
					s = Unigine::Math::max(s, states[i]->getState());
				}

				state = s;
			}
	};

	// 控制按键的输入操作列表
	InputAction actions[6] =
	{
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_W) })),
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_S) })),
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_A) })),
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_D) })),
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_SPACE) })),
		InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardDown(Unigine::Input::KEY::KEY_F5) })),
	};

protected:
	主循环重写方法
	void init();
	void update();
};
InputController.cpp
#include "InputController.h"
REGISTER_COMPONENT(InputController);

InputController* InputController::instance = nullptr;

void InputController::init()
{
	instance = this;
}

// 每帧更新所有操作的状态
void InputController::update()
{
	for(InputAction action : actions)
	{
		action.update();
	}
}

InputController* InputController::getInstance() 
{
	return instance;
};

float InputController::getAction(InputController::INPUT_ACTION_TYPE action)
{
	if (instance == nullptr)
		return 0.0f;
	if (!instance->isEnabled())
		return 0.0f;
	instance->actions[(int)action].update();

	return instance->actions[(int)action].state;
}

Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.保存我们修改过的所有文件,然后按下Ctrl + F5来构建并运行应用程序,从而使组件系统生成可用于将组件分配到节点的属性。运行完后关闭应用程序,并切换回 UnigineEditor。

Create a new Node Dummy and name it Input. This node will be responsible for processing input events, so assign the InputController component to it.创建一个新的Node Dummy节点,并将其命名为Input。该节点将负责处理输入事件,因此请为其分配InputController组件。

Now we need to combine the processing of input events and car control, so create the CarPlayer component inherited from Car:现在我们需要将输入事件处理与汽车控制结合起来,因此请创建一个继承自CarCarPlayer组件:

CarPlayer.h
#pragma once
#include "Car.h"
#include "InputController.h"
#include <UnigineComponentSystem.h>
class CarPlayer :
    public Car
{
public:
	// 组件构造函数和方法列表
	COMPONENT_DEFINE(CarPlayer, Car);
	// -------------------------------
	COMPONENT_UPDATE(update,2);

protected:
	主循环重写方法
	void update();
};
CarPlayer.cpp
#include "CarPlayer.h"
#include "InputController.h"
#include <UnigineComponentSystem.h>
using namespace Unigine;
REGISTER_COMPONENT(CarPlayer);

void CarPlayer::update()
{
	// 设置方向盘转向
	float wheel = 0.0f;

	wheel -= InputController::getAction(InputController::INPUT_ACTION_TYPE::WHEEL_RIGHT);
	wheel += InputController::getAction(InputController::INPUT_ACTION_TYPE::WHEEL_LEFT);

	setWheelPosition(wheel);

	// 刹车和油门
	float brake = InputController::getAction(InputController::INPUT_ACTION_TYPE::BRAKE);
	float throttle = InputController::getAction(InputController::INPUT_ACTION_TYPE::THROTTLE);

	// 当速度方向、刹车或油门状态变化时,切换驾驶模式
	float velocity = getSpeed() * (getCurrentMoveDirection() == Car::MOVE_DIRECTION::FORWARD ? 1 : -1);

	if (velocity > 0.0f)
	{
		if (velocity < 1.25f && brake > Math::Consts::EPS && throttle < Math::Consts::EPS)
			setMoveDirection(Car::MOVE_DIRECTION::REVERSE);
	}
	else
	{
		if (velocity > -1.25f && throttle > Math::Consts::EPS && brake < Math::Consts::EPS)
			setMoveDirection(Car::MOVE_DIRECTION::FORWARD);
	}

	// 在倒车模式下,油门和刹车是相反的
	if (getCurrentMoveDirection() == Car::MOVE_DIRECTION::REVERSE)
	{
		float t = brake;
		brake = throttle;
		throttle = t;
	}

	// 设置油门和刹车
	setThrottle(throttle);
	setBrake(brake);
	// 设置手刹:根据按键状态,或在输入控制器被禁用(例如游戏结束)时强制开启
	setHandBrake(InputController::getInstance()->isEnabled() ? InputController::getAction(InputController::INPUT_ACTION_TYPE::HAND_BRAKE) : 1.0f);

	Car::update();
}

Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.保存我们修改过的所有文件,然后按下Ctrl + F5来构建并运行应用程序,从而使组件系统生成可用于将组件分配到节点的属性。运行完后关闭应用程序,并切换回 UnigineEditor。

Assign this component to the pickup_frame node and customize the car parameters as you need, for example use the following values:将此组件分配给pickup_frame节点,并根据需要自定义汽车参数,例如可以使用以下数值:

Make sure to specify the wheel nodes from the wheels group by dragging each node to the corresponding field. Do the same with the nodes for the brake light and reverse light. The Car component is responsible for turning these lights on and off, so all the lights except the daytime running lights (the light node) should be turned off.确保从wheels组中将所有轮子节点拖入各自对应的字段中。刹车灯和倒车灯的节点也要以相同方式拖入对应字段。Car组件负责控制这些灯的开关,因此除了日间行车灯(light节点)之外,其它灯应当初始设为关闭状态。

Let's launch the application and enjoy driving.现在,让我们启动应用程序,享受驾驶的乐趣吧!

本页面上的信息适用于 UNIGINE 2.20 SDK.

最新更新: 2025-06-20
Build: ()