头文件 Character.h
#include "ChracterController.h"
using namespace Ogre;
class Character: public FrameListener
{
public:
Character()
{
startup();
loadResources();
setupContent();
setupControl();
_root->addFrameListener(this); //通过这句,把自己加入到帧监听队列中,否则该类的几个监听函数都会不好使
}
~Character()
{
_man->destroyInputObject(_key);
_man->destroyInputObject(_mouse);
OIS::InputManager::destroyInputSystem(_man);
cleanupContent();
}
/*************************我们四是来客串的*************************/
void loadResources()
{
Ogre::ConfigFile cf; //助手类,用于加载配置文件
cf.load("resources_d.cfg"); //载入配置文件
Ogre::ConfigFile::SectionIterator sectionIter = cf.getSectionIterator();//得到一个遍历块的迭代器
Ogre::String sectionName, typeName, dataname; //块名,键,值
//开始通过迭代器遍历资源
while (sectionIter.hasMoreElements())
{
sectionName = sectionIter.peekNextKey(); //得到块
Ogre::ConfigFile::SettingsMultiMap *settings = sectionIter.getNext(); //得到map
Ogre::ConfigFile::SettingsMultiMap::iterator i; //定义一个map的迭代器
for (i = settings->begin(); i != settings->end(); ++i) //对块里头的键-值进行迭代
{
typeName = i->first; //获得键
dataname = i->second; //获得值
//根据块,键,值来载入资源
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(dataname, typeName, sectionName);
}
}
//初始化所有的资源组
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}
int startup()
{
_root = new Ogre::Root("plugins_d.cfg"); //载入插件
if(!_root->showConfigDialog()) //弹出黑框框,如果出错或者用户点取消则返回
{
return -1;
}
//通过root,初始化-----渲染窗口和场景管理器
mWindow = _root->initialise(true,"Ogre3D Beginners Guide");
mSceneMgr = _root->createSceneManager(Ogre::ST_GENERIC);
//天空盒资源暂时还不知道如何载入,所以不能调用天空盒
//_sceneManager->setSkyBox(true, "Examples/SpaceSkyBox", 50 );
//通过场景管理器实例化------摄像头
mCamera = mSceneMgr->createCamera("Camera");
//通过渲染窗口和摄像机实例化------视口
mViewport = mWindow->addViewport(mCamera);
return 0;
}
void renderOneFrame()
{
WindowEventUtilities::messagePump();
mKeepRunning = _root->renderOneFrame();
}
void setupControl()
{
OIS::ParamList pl;
unsigned int windowHandle = 0;
std::ostringstream windowHandleStr; //该类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代sprintf
mWindow->getCustomAttribute("WINDOW",&windowHandle);
windowHandleStr << windowHandle; //格式化
pl.insert(std::make_pair("WINDOW",windowHandleStr.str()));
_man = OIS::InputManager::createInputSystem(pl);
_key = static_cast<OIS::Keyboard*>(_man->createInputObject(OIS::OISKeyboard,false));
_mouse = static_cast<OIS::Mouse*>(_man->createInputObject(OIS::OISMouse,false));
}
/**********************FrameListener的三大函数******************/
bool frameStarted(const FrameEvent& evt)
{
return true;
}
bool frameRenderingQueued(const FrameEvent& evt)
{
mouseMoved();
keyPressed();
keyReleased();
mousePressed(evt);
mChara->addTime(evt.timeSinceLastFrame);
//你妹啊 时间完全没被监听,玩啥子,明天再战
return true;
}
bool frameEnded(const FrameEvent& evt)
{
return true;
}
/******************************鼠标键盘的控制*******************/
bool keyPressed()
{
mChara->injectKeyDown(_key);
return true;
}
bool keyReleased()
{
mChara->injectKeyUp(_key);
return true;
}
bool mouseMoved()
{
mChara->injectMouseMove(_mouse);
return true;
}
bool mousePressed(const FrameEvent& evt)
{
mChara->injectMouseDown(_mouse,evt);
return true;
}
protected: //为什么用protect?
void setupContent()
{
mViewport->setBackgroundColour(ColourValue(1.0f, 1.0f, 0.8f));
mSceneMgr->setFog(Ogre::FOG_LINEAR, ColourValue(1.0f, 1.0f, 0.8f), 0, 15, 100);
mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE);
mSceneMgr->setShadowColour(ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setShadowTextureSize(1024);
mSceneMgr->setShadowTextureCount(1);
mSceneMgr->setAmbientLight(ColourValue(0.3, 0.3, 0.3));
Light* light = mSceneMgr->createLight();
light->setType(Light::LT_POINT);
light->setPosition(-10, 40, 20);
light->setSpecularColour(ColourValue::Blue);
// create a floor mesh resource
MeshManager::getSingleton().createPlane("floor", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Plane(Vector3::UNIT_Y, 0), 300, 300, 10, 10, true, 1, 10, 10, Vector3::UNIT_Z);
// create a floor entity, give it a material, and place it at the origin
Entity* floor = mSceneMgr->createEntity("Floor", "floor");
floor->setMaterialName("Examples/Rockwall");
floor->setCastShadows(false);
mSceneMgr->getRootSceneNode()->attachObject(floor);
// create our character controller
mChara = new CharacterController(mCamera,_key,_mouse);
}
void cleanupContent()
{
if (mChara)
{
delete mChara;
}
MeshManager::getSingleton().remove("floor");
}
CharacterController* mChara;
/********客串*******/
Root* _root;
SceneManager* mSceneMgr;
Viewport* mViewport;
RenderWindow* mWindow;
Camera* mCamera;
bool mKeepRunning;
OIS::InputManager* _man;
OIS::Keyboard* _key;
OIS::Mouse* _mouse;
};
#include "OGRE/Ogre.h"
#include "OIS/OIS.h"
using namespace Ogre;
#define NUM_ANIMS 20 // number of animations the character has
#define CHAR_HEIGHT 0 // height of character's center of mass above ground
#define CAM_HEIGHT 10 // height of camera above character's center of mass
#define RUN_SPEED 17 // character running speed in units per second
#define TURN_SPEED 500.0f // character turning in degrees per second
#define ANIM_FADE_SPEED 70.5f // animation crossfade speed in % of full weight per second
#define JUMP_ACCEL 30.0f // character jump acceleration in upward units per squared second
#define GRAVITY 90.0f // gravity in downward units per squared second
class CharacterController
{
private:
enum AnimID
{
ANIM_IDLE1,
ANIM_IDLE2,
ANIM_IDLE3,
ANIM_WALK,
ANIM_ATTACK1,
ANIM_ATTACK2,
ANIM_ATTACK3,
ANIM_HIGHJUMP,
ANIM_JUMP,
ANIM_JUMPNOHEIGHT,
ANIM_DEATH1,
ANIM_DEATH2,
ANIM_BACKFLIP,
ANIM_BLOCK,
ANIM_CLIMB,
ANIM_CROUCH,
ANIM_KICK,
ANIM_SIDEKICK,
ANIM_SPIN,
ANIM_STEALTH,
ANIM_NONE
};
OIS::InputManager* mMan;
OIS::Keyboard* mKey;
OIS::Mouse* mMouse;
public:
/********************以下两个是主要的控制函数*******************/
//初始化人物,摄像机和动画
CharacterController(Camera* cam,OIS::Keyboard* key,OIS::Mouse* mouse)
{
mKey = key;
mMouse = mouse; //构造函数里头预先赋值的鼠标键盘好像到后来渲染的时候就变成空指针了
setupBody(cam->getSceneManager());
setupCamera(cam);
setupAnimations();
}
//随时间来控制动画
void addTime(Real deltaTime)
{
updateAnimations(deltaTime);
updateBody(deltaTime);
updateCamera(deltaTime);
}
/********************害羞分割线*********************************/
/********************以下是鼠标键盘的控制函数*******************/
void injectKeyDown(OIS::Keyboard* key)
{
/*if (mKeyDirection.isZeroLength() && mBaseAnimID != ANIM_IDLE3 //为了不让他一直加载IDLE3
&& mBaseAnimID!= ANIM_JUMP) //为了不让他在跳起来时候被IDLE3
{
setBaseAnimation(ANIM_IDLE3, true);
}*/
//换一种写法
//定在原地发呆的三个条件:不移动+不在发呆中+没有在做非循环动作
//因为先是进行判定,然后才是给mTimer加上deltaTime,所以不用担心,循环动画的时候一定是为0的
//循环动画:发呆系列+行走系列
//我把这段判断放到最前面来了,比较保险
if (mKeyDirection.isZeroLength() && mBaseAnimID != ANIM_IDLE3 && mTimer == 0)
{
setBaseAnimation(ANIM_IDLE3, true);
}
mKey = key;
mKey->capture();
bool isWalk = false;
//开始合成方向向量
if (mKey->isKeyDown(OIS::KC_W))
{
mKeyDirection.z = -1;
}
if (mKey->isKeyDown(OIS::KC_A))
{
mKeyDirection.x = -1;
}
if (mKey->isKeyDown(OIS::KC_S))
{
mKeyDirection.z = 1;
}
if (mKey->isKeyDown(OIS::KC_D))
{
mKeyDirection.x = 1;
}
if (!mKeyDirection.isZeroLength() && mBaseAnimID == ANIM_IDLE3)
{
isWalk = true;
if (mKey->isKeyDown(OIS::KC_LSHIFT))
{
setBaseAnimation(ANIM_STEALTH, true);
}
else
{
setBaseAnimation(ANIM_WALK, true);
}
}
//非循环动画需要满足:触发条件+计时器为0
//当有非循环动画在播放时,计时器不为0
if (mKey->isKeyDown(OIS::KC_SPACE) && mTimer == 0)
{
setBaseAnimation(ANIM_JUMP, true);
mActionDirection = Vector3(0,0,5);
}
if (mKey->isKeyDown(OIS::KC_E) && mTimer == 0)
{
setBaseAnimation(ANIM_SPIN, true);
mActionDirection = Vector3(0,0,1);
}
if (mKey->isKeyDown(OIS::KC_Q) && mTimer == 0)
{
setBaseAnimation(ANIM_BACKFLIP, true);
mActionDirection = Vector3(0,0,-1);
}
if (mKey->isKeyDown(OIS::KC_F) && mTimer == 0)
{
setBaseAnimation(ANIM_HIGHJUMP, true);
mActionDirection = Vector3(0,0,2);
}
}
void injectKeyUp(OIS::Keyboard* key)
{
mKey = key;
mKey->capture();
if (!mKey->isKeyDown(OIS::KC_W)&& mKeyDirection.z == -1)
{
mKeyDirection.z = 0;
}
if (!mKey->isKeyDown(OIS::KC_A)&&mKeyDirection.x == -1)
{
mKeyDirection.x = 0;
}
if (!mKey->isKeyDown(OIS::KC_S)&& mKeyDirection.z == 1)
{
mKeyDirection.z = 0;
}
if (!mKey->isKeyDown(OIS::KC_D)&& mKeyDirection.x == 1)
{
mKeyDirection.x = 0;
}
}
void injectMouseMove(OIS::Mouse* mouse)
{
mMouse = mouse;
mMouse->capture();
updateCameraGoal(-0.05f * mMouse->getMouseState().X.rel, -0.05f * mMouse->getMouseState().Y.rel,0.005f * mMouse->getMouseState().Z.rel);
}
void injectMouseDown(OIS::Mouse* mouse,const FrameEvent& evt)
{
mMouse = mouse;
mMouse->capture();
if (mMouse->getMouseState().buttonDown(OIS::MB_Left))
{
if (mTimer == 0)
{
if (!act3[0])
{
setBaseAnimation(ANIM_ATTACK1,true);
mActionDirection = Vector3(0,0,0.5);
act3[0] = true;
}
else if(act3[0] == true && !act3[1])
{
setBaseAnimation(ANIM_SPIN,true);
mActionDirection = Vector3(0,0,1);
act3[1] = true;
}
else if (act3[1] == true)
{
setBaseAnimation(ANIM_HIGHJUMP,true);
mActionDirection = Vector3(0,0,2);
act3[0] = false;
act3[1] = false;
}
}
actTime = 0; //我知道你点了
}
if (mMouse->getMouseState().buttonDown(OIS::MB_Right) && mTimer == 0)
{
setBaseAnimation(ANIM_KICK,true);
mActionDirection = Vector3(0,0,0.3);
}
actTime += evt.timeSinceLastFrame; //连击君很努力的在计时
if (actTime > mAnims[ANIM_ATTACK1]->getLength())
{
act3[0] = false;
act3[1] = false;
}
}
/**************************羞涩分割线**********************************/
private:
/*******************对两个主要函数的子函数进行实现**********************/
void setupBody(SceneManager* sceneMgr)
{
mNinjaNode = sceneMgr->createSceneNode("NinjaNode");
mBodyNode = sceneMgr->getRootSceneNode()->createChildSceneNode(Vector3::UNIT_Y * CHAR_HEIGHT);
mBodyEnt = sceneMgr->createEntity("ninja", "ninja.mesh");
mNinjaNode->attachObject(mBodyEnt);
mNinjaNode->yaw(Degree(180));
mBodyNode->addChild(mNinjaNode);
mBodyNode->setScale(0.05f,0.05f,0.05f);
//mBodyNode->setPosition(0,-10,0);
mKeyDirection = Vector3::ZERO;
mVerticalVelocity = 0;
}
void setupCamera( Camera* cam )
{
//一个大致的轴,摄像机可以绕其旋转
mCameraPivot = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();
//以轴节点为父空间,向Z轴正方向移一定距离,人当然是往负方向走咯
mCameraGoal = mCameraPivot->createChildSceneNode(Vector3(0, 0, 25));
//这才是摄像机真正的位置
mCameraNode = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();
//摄像机位置 = 轴的位置 + 轴往Z轴正方向的位置;也就是说,摄像机位于轴节点的后脑勺 =-=
mCameraNode->setPosition(mCameraPivot->getPosition() + mCameraGoal->getPosition());
//这三句有待观察,先注释掉------之后感觉这三句话有拯救世界的能力,如果去掉的话,镜头会不稳
mCameraPivot->setFixedYawAxis(true);
mCameraGoal->setFixedYawAxis(true);
mCameraNode->setFixedYawAxis(true);
cam->setNearClipDistance(0.1);
cam->setFarClipDistance(100);
mCameraNode->attachObject(cam); //把摄像机绑到节点上
mPivotPitch = 0; //这个是做什么的?
}
void setupAnimations()
{
mBodyEnt->getSkeleton()->setBlendMode(ANIMBLEND_CUMULATIVE);
mActionDirection = Vector3::ZERO; //初始动作序列为0
act = 0; //连击计数器清零
actTime = 0;
act3[0] = false;
act3[1] = false;
String animNames[] =
{
"Idle1","Idle2","Idle3","Walk",
"Attack1","Attack2","Attack3",
"HighJump","Jump","JumpNoHeight",
"Death1","Death2",
"Backflip","Block","Climb","Crouch",
"Kick","SideKick",
"Spin","Stealth"
};
for (int i = 0; i < NUM_ANIMS; i++)
{
mAnims[i] = mBodyEnt->getAnimationState(animNames[i]);
mAnims[i]->setLoop(true);
mFadingIn[i] = false;
mFadingOut[i] = false;
}
setBaseAnimation(ANIM_IDLE3);
mAnims[ANIM_IDLE3]->setEnabled(true);
}
void updateBody( Real deltaTime )
{
mGoalDirection = Vector3::ZERO; // we will calculate this
if (mKeyDirection != Vector3::ZERO)
{
// calculate actually goal direction in world based on player's key directions
//这里可以理解为基于摄像机的角色旋转
//我们用摄像机四元组加上我们的合成向量,就能得到我们要前景的方向
mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis();
mGoalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis();
mGoalDirection.y = 0;
//单位化
mGoalDirection.normalise();
//获得一个新的四元组
Quaternion toGoal = mBodyNode->getOrientation().zAxis().getRotationTo(mGoalDirection);
// calculate how much the character has to turn to face goal direction
Real yawToGoal = toGoal.getYaw().valueDegrees();
// this is how much the character CAN turn this frame
Real yawAtSpeed = yawToGoal / Math::Abs(yawToGoal) * deltaTime * TURN_SPEED;
// reduce "turnability" if we're in midair
//if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;
// turn as much as we can, but not more than we need to
if (yawToGoal < 0) //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
{
yawToGoal = std::min<Real>(0, std::max<Real>(yawToGoal, yawAtSpeed));
}
else if (yawToGoal > 0) yawToGoal = std::max<Real>(0, std::min<Real>(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
mBodyNode->yaw(Degree(yawToGoal));
// move in current body direction (not the goal direction)
if (mBaseAnimID == ANIM_STEALTH)
{
mBodyNode->translate(0, 0, deltaTime * RUN_SPEED * 0.3,Node::TS_LOCAL);
}
else
{
mBodyNode->translate(0, 0, deltaTime * RUN_SPEED,Node::TS_LOCAL);
}
}
if (mActionDirection != Vector3::ZERO)
{
mBodyNode->translate(0, 0, deltaTime * RUN_SPEED * mActionDirection.z ,Node::TS_LOCAL);
}
/*if (mBaseAnimID == ANIM_JUMP_LOOP)
{
// if we're jumping, add a vertical offset too, and apply gravity
mBodyNode->translate(0, mVerticalVelocity * deltaTime, 0, Node::TS_LOCAL);
mVerticalVelocity -= GRAVITY * deltaTime;
Vector3 pos = mBodyNode->getPosition();
if (pos.y <= CHAR_HEIGHT)
{
// if we've hit the ground, change to landing state
pos.y = CHAR_HEIGHT;
mBodyNode->setPosition(pos);
setBaseAnimation(ANIM_JUMP_END, true);
mTimer = 0;
}
}*/
}
void updateAnimations(Real deltaTime)
{
Real baseAnimSpeed = 1;
Real topAnimSpeed = 1;
mTimer += deltaTime;
// increment the current base and top animation times
if (mBaseAnimID != ANIM_NONE)
{
mAnims[mBaseAnimID]->addTime(deltaTime * baseAnimSpeed);
}
//这三个动作是循环动画,意思为,我已经循环了,大哥你就不要给我计时了
if (mBaseAnimID != ANIM_IDLE3 && mBaseAnimID != ANIM_WALK && mBaseAnimID != ANIM_STEALTH)
{
//若为非循环,时间到了就开始发呆
if (mTimer >= mAnims[mBaseAnimID]->getLength())
{
setBaseAnimation(ANIM_IDLE3);
mActionDirection = Vector3::ZERO; //动画播放完毕,也不要位移了
}
else
{
mKeyDirection = Vector3.ZERO; //若是非循环动画,我们让键盘移动失效
}
}
else
{
//如果是循环动作,则保持计时器为0
mTimer = 0;
}
// apply smooth transitioning between our animations
fadeAnimations(deltaTime);//这个不加进去的话动画就播放不了
}
void updateCamera( Real deltaTime )
{
//人物在哪,我就在他身后
mCameraPivot->setPosition(mBodyNode->getPosition() + Vector3::UNIT_Y * CAM_HEIGHT);
// 这里我们获得goal的世界坐标,用_getDerivedPosition,如果用getPosition的话,只能获得父空间的坐标,那就没啥用了
Vector3 goalOffset = mCameraGoal->_getDerivedPosition() - mCameraNode->getPosition();
//慢慢的移丫移,这里的9.0f对摄像头最终的位置没有影响,只会影响摄像机移动的快慢,可以称为灵敏度吧
mCameraNode->translate(goalOffset * deltaTime * 9.0f);
// 摄像机节点总是看向轴节点
mCameraNode->lookAt(mCameraPivot->_getDerivedPosition(), Node::TS_WORLD);
}
void updateCameraGoal(Real deltaYaw, Real deltaPitch, Real deltaZoom) //还有个z轴的让我删去了,鼠标好像没有Z轴吧,Z轴好像是缩放的意思.囧
{
mCameraPivot->yaw(Degree(deltaYaw), Node::TS_WORLD);
// bound the pitch
if (!(mPivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
!(mPivotPitch + deltaPitch < -60 && deltaPitch < 0))
{
mCameraPivot->pitch(Degree(deltaPitch), Node::TS_LOCAL);
mPivotPitch += deltaPitch;
}
Real dist = mCameraGoal->_getDerivedPosition().distance(mCameraPivot->_getDerivedPosition());
Real distChange = deltaZoom * dist;
// bound the zoom
if (!(dist + distChange < 8 && distChange < 0) &&
!(dist + distChange > 25 && distChange > 0))
{
mCameraGoal->translate(0, 0, distChange, Node::TS_LOCAL);
}
}
/***************************不要问我为什么出现在这里*************/
//不是太清楚这段函数是什么意思Mark
void fadeAnimations(Real deltaTime)
{
for (int i = 0; i < NUM_ANIMS; i++)
{
if (mFadingIn[i])
{
// slowly fade this animation in until it has full weight
//简而言之,就是mFadingOut[i]为TRUE的时候,要把模型慢慢加载进来
//貌似newWeight是0~1或者比1大的一个数,就是说当newWeight大于1的时候就完全加载了
Real newWeight = mAnims[i]->getWeight() + deltaTime * ANIM_FADE_SPEED;
mAnims[i]->setWeight(Math::Clamp<Real>(newWeight, 0, 1));//这句为了使weight在0~1之间
if (newWeight >= 1) mFadingIn[i] = false;
}
else if (mFadingOut[i])
{
// slowly fade this animation out until it has no weight, and then disable it
//简而言之,就是mFadingOut[i]为TRUE的时候,要把模型慢慢卸载掉
Real newWeight = mAnims[i]->getWeight() - deltaTime * ANIM_FADE_SPEED;
mAnims[i]->setWeight(Math::Clamp<Real>(newWeight, 0, 1));
if (newWeight <= 0)
{
mAnims[i]->setEnabled(false);
mFadingOut[i] = false;
}
}
}
}
void setBaseAnimation(AnimID id, bool reset = false)
{
//这里的mBaseAnimID是表示上一个动画
if (mBaseAnimID >= 0 && mBaseAnimID < NUM_ANIMS)
{
// if we have an old animation, fade it out
mFadingIn[mBaseAnimID] = false;
mFadingOut[mBaseAnimID] = true;
}
//把即将要播放的动画传进来
mBaseAnimID = id;
//如果新动画不为空的话
if (id != ANIM_NONE)
{
// if we have a new animation, enable it and fade it in
mAnims[id]->setEnabled(true);
mAnims[id]->setWeight(0);
mFadingOut[id] = false;
mFadingIn[id] = true;
if (reset)
{
mAnims[id]->setTimePosition(0);
}
}
}
void setTopAnimation(AnimID id, bool reset = false)
{
}
Camera* mCamera;
SceneNode* mNinjaNode;
SceneNode* mBodyNode;
SceneNode* mCameraPivot;
SceneNode* mCameraGoal;
SceneNode* mCameraNode;
Real mPivotPitch; //暂时不知道做什么用的
Entity* mBodyEnt;
Entity* mSword1;
Entity* mSword2;
AnimationState* mAnims[NUM_ANIMS];
AnimID mBaseAnimID;
AnimID mTopAnimID;
bool mFadingIn[NUM_ANIMS]; //貌似是为了动画间平滑过度什么的,暂时不是很清楚
bool mFadingOut[NUM_ANIMS];
bool mSwordsDrawn;
Vector3 mKeyDirection; //玩家通过WASD产生的向量
Vector3 mGoalDirection; //未知->搞明白但是忘了,得回头看看
Real mVerticalVelocity; //跳时候用的,未知
Real mTimer; //计时器,很重要的
/**********DIY的*********/
Vector3 mActionDirection; //做出各种动作而产生的位移
int act; //连击计数器->事实证明计数器没有标志器好使
float actTime; //连击超时器
bool act3[2]; //3连击标志器
};
源文件:Character.cpp
#include "Character.h"
int main()
{
Character app;
while (1)
{
app.renderOneFrame();
}
return 0;
}