所谓游戏主角就是第三视角看到的在屏幕中间附近跑来跑去,然后周围景物随之变换的节点。
实现的思路很简单,分为以下三步
1. 获取键盘输入
2. 移动主角,并且在需要的时候更改动画
3. 移动摄像机
这里的前提是周围环境相对世界坐标系不变。
那么使用Irrlicht怎么实现呢?一步一步来。
1. 获取键盘输入
自然就是重载我们的 IEventReceiver 类了,这部分大体思路可以参考教程 04.Movement 里面的实现:维护一个键位表,每个键值有两个状态(按下和没有按下),在键盘有信号时修改本表对应键值的状态,以便主渲染程序读取。
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
runContrlCounter = 0;
pressKeyCounter = 0;
}
virtual bool OnEvent(const SEvent& event)
{
// 改变键位状态
if (event.EventType == irr::EET_KEY_INPUT_EVENT){
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
}
return false;
}
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
private:
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};
接下来第二步
2.移动角色并更改动画
本身这几个动作是比较简单的,但坑就坑在节点类 IAnimatedMeshSceneNode 居然是个抽象类,并且我没有找到合适的去继承它成为实体类的方法(如果有请告知)。那只能对这个类的系列操作封装在一个代理类 MyRoleDelegate 里面了。这个类里面有个 render() 函数用于从事件接收器中读取当前键盘状态并操作主节点。
有WASD操作的人物一共有八个方向,此时对应八种模型旋转变换(二维),也比较简单。附上代码
class MyRoleDelegate
{
public:
MyRoleDelegate(IAnimatedMeshSceneNode* node_p, MyEventReceiver* receiver_p,
IrrlichtDevice* device_p)
:role(node_p), receiver(receiver_p), device(device_p)
{
init();
}
void init()
{
MOVEMENT_SPEED = 25.f;
available = true;
currentPosition = role->getPosition();
setAnimationType(EMAT_STAND);
//初始为站立动画
}
void setAnimationType(EMD2_ANIMATION_TYPE atype)
{
//避免重复设置同一个动作而造成卡顿
if(currentAnimationType!=atype)
{
role->setMD2Animation(atype);
currentAnimationType=atype;
}
}
void render()
{
if(false)
{
// setAnimationType(EMAT_STAND);
}
else
{
setAnimationType(EMAT_RUN);
f32 frameDeltaTime = 0.1;
currentPosition = role->getPosition();
float move_distance = MOVEMENT_SPEED * frameDeltaTime;
vector3df dir(0.0,-90,0); //角色的方向
if(receiver->IsKeyDown(KEY_KEY_W))
{
dir.Y = -90;
currentPosition.Z += move_distance*0.7;
if(receiver->IsKeyDown(KEY_KEY_A))
{ //WA 方向
dir.Y = -135;
currentPosition.X -= move_distance*0.7;
}
else if(receiver->IsKeyDown(KEY_KEY_D))
{ //WD 方向
dir.Y = -45;
currentPosition.X += move_distance*0.7;
}
else
{//W方向,以下类似
currentPosition.Z += move_distance*0.3;
}
}
else if(receiver->IsKeyDown(KEY_KEY_S))
{
dir.Y = 90;
currentPosition.Z -= move_distance*0.7;
if(receiver->IsKeyDown(KEY_KEY_A))
{
dir.Y = 135;
currentPosition.X -= move_distance*0.7;
}
else if(receiver->IsKeyDown(KEY_KEY_D))
{
dir.Y = 45;
currentPosition.X += move_distance*0.7;
}
else
{
currentPosition.Z -= move_distance*0.3;
}
}
else if(receiver->IsKeyDown(KEY_KEY_A))
{
dir.Y = 180;
currentPosition.X -= move_distance;
}
else if(receiver->IsKeyDown(KEY_KEY_D)){
dir.Y = 360;
currentPosition.X += move_distance;
}
else{
dir = currentRotation;
setAnimationType(EMAT_STAND);
}
if(dir != currentRotation){
role->setRotation(dir);
currentRotation = dir;
}
role->setPosition(currentPosition);
}
}
vector3df getCameraPosition(){
//设置此时相机应该处于的位置
vector3df pos = currentPosition;
pos.Y += 50;
pos.Z -= 50;
return pos;
}
private:
IrrlichtDevice* device; //主设备指针
MyEventReceiver* receiver;//主类事件接收器
IAnimatedMeshSceneNode* role;//所代理的节点指针
bool available;
EMD2_ANIMATION_TYPE currentAnimationType;
vector3df currentPosition, currentRotation;;
f32 MOVEMENT_SPEED;
};
另外,setAnimationType涉及更改动画的API,仅仅适用于MD2格式模型,如有其它需要请重载
第三步就最简单了,只需要在主渲染函数里面更改相机位置即可。
while(device->run())
{
rDele->render(); //rDele 为主节点代理
camera->setPosition(rDele->getCameraPosition());
//其他渲染操作
}
当然,你还需要一个主Game类来初始化所有变量,载入模型,建立三角碎片选择器,加入碰撞检测与重力等等,就不在这里解释了。
最终的效果就是开头的那张动画。