这是款联机版3D桌球游戏,带有自动挂机算法,支持单机npc和联网对战。添加了瞄准辅助线功能。统一的架构,可以方便的嵌入rpg中。选手模型可以使用mmorpg中的玩家的模型。

选手动画暂时只做了一个站姿击球和一个坐姿击球,当选手和桌面发生碰撞时会从站姿切换到坐姿,中间自动添加过渡动画。手部动画配合拉杆力度使用计算预制关键帧的方法。鼠标控制拉杆角度选手移动位置时,脚步动作使用上下分身控制不同的动作。

碰撞计算是离散的,没有使用物理引擎,没有考虑到转动惯量的影响,这里是一个可以改进的地方(目前默认击球点在球的正中部位)。

AI算法,寻找最佳目标球(白色球-目标球-某个袋 - 距离之和越小越好, 夹角越小越好,行进路线上不能有干扰球)。

球的碰撞及运动只同步了初始状态, 后续的运动本机模拟,正常情况下简单的实现了同步。碰撞结果也暂时本机模拟,有待host处理。 同步了玩家瞄准动画。

unity 台球游戏_#include

  

unity 台球游戏_unity 台球游戏_02

 

unity 台球游戏_unity 台球游戏_03

 

源代码:

 游戏类

//========================================================
//  @Date:     2016.05
//  @File:     SourceDemoClient/Billiards/MiniGameBilliards.h
//  @Brief:     MiniGameBilliards
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================
 

#ifndef  __MiniGameBilliards__H__
#define  __MiniGameBilliards__H__

#include "Billiards/BilliardsBall.h"
#include "Rpg/MiniGame.h"
#include "Render/Texture.h"

#define BallCount 16    //球个数
#define pocketRadius 3  //球袋半径

//球ID
enum BallID
{
	WhiteBall = 0,
	yellow_solidBall,
	blue_solidBall,
	red_solidBall,
	purple_solidBall,
	orange_solidBall,
	green_solidBall,
	brown_solidBall,

	BlackBall = 8,

	yellow_stripeBall,
	blue_stripeBall,
	red_stripeBall,
	purple_stripeBall,
	orange_stripeBall,
	green_stripeBall,
	brown_stripeBall,
};

//队
enum TeamSide
{
	TeamSide_Null=-1,
	TeamSide_Stripe = 0,
	TeamSide_Solid,
};

//为了避免计算误差引起的不同步,otherplayer击球引发的ball rolling计算在本机不处理碰撞,碰撞信息由otherplayer发过来,否则小的角度误差导致不同步。
enum MiniBilliardsCmd
{
	CMD_PrepareShoot,//准备击球 同步所有球的位置
	CMD_StickMove,   //同步当前玩家杆子角度
	CMD_ShootStick  ,//击球
	//CMD_BallMove    ,//球碰撞及运动 自己模拟即可无需同步
	CMD_BallIn      ,//进球  同步结果避免差异或外挂    
	CMD_GameOver    ,
	CMD_Restart     ,
};
const char* BilliardsCmdToString(int enumeration);

class BilliardsPlayer;
class BilliardsPlayerRole;
class SoundChannel;

class MiniGameBilliards:public MiniGame
{
public:
	MiniGameBilliards();
	virtual~MiniGameBilliards();
	virtual bool Start();
	virtual bool Stop();
	virtual bool Render();
	virtual void RenderUI();
	virtual bool Update();
	virtual bool Free();
	virtual bool KeepResource(bool once,int& circle,String& nextTip);

	//三种类型结构
	virtual MiniPlayer*  CreatePlayer();
	virtual MiniPlayer*  CreateRobot ();
	virtual MiniPlayer*  CreateRole  ();

	void DeflectBalls(BilliardsBall& left, BilliardsBall&right);
	void PocketInBall(BilliardsBall& ball,int pocket);

	//某方进球数
	int  InPocketNum(TeamSide side);

	//桌台边框碰撞点
	vec3 GetBoundPosFromInner(const vec3& pos,const vec3& dir);
	vec3 GetBoundPosFromOut(const vec3& pos,const vec3& dir);

	//处理游戏网络命令包
	virtual int  ProcessPacketCmd(PacketBase* packet);
	//virtual const char* CmdToString(const char* stream,int len);
	//发送推杆消息
	bool SendShootStick(float shootAngleRad,float shootPower);
	//发送瞄准动画消息
	bool SendStickMove(float shootAngleRad,float stickDistance);

	bool ShootStick(float shootAngleRad,float shootPower);

	bool SendPrepareShooting();

	//获得球所属的 实心还是花
	bool IsTeamSide(BallID id,TeamSide side);

	BilliardsPlayer* GetTurnPlayer();	

	enum GameState
	{
		PrepareShooting,
		BallRolling,
		Shooting,
	};
	GameState m_GameState;
	BilliardsPlayerRole* m_myRolePlayer;
	BilliardsBall    m_balls[BallCount];

	vec3  m_worldDeskMin;
	vec3  m_worldDeskMax;
	vec3  m_worldDeskCen;

	vec3  WhiteStartPos;
	vec3  PocketPos[6];

	int   m_curTurn;
	float m_prepareTime;           //准备时间
	float m_simulateTime;

private:
	RendSys::MovieClip* m_movieStick[2];
	RendSys::MovieClip* m_movieBalls[BallCount];

	RendSys::MovieClip* m_movieBestPocket;

	TexturePtr m_ballIcons[BallCount];
	TexturePtr m_powerBar;
	TexturePtr m_lazer;

	//黑8是否进洞
	bool       m_blackInPacket; 
	float      m_oldFov;
};

extern MiniGameBilliards* G_BilliardsGame;

#endif


//========================================================
//  @Date:     2016.05
//  @File:     SourceDemoClient/Billiards/MiniGameBilliards.cpp
//  @Brief:     MiniGameBilliards
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================

#include "General/Pch.h"
#include "General/General.h"
#include "General/StringUtil.h"
#include "General/Timer.h"
#include "General/Window.h"
#include "Gui/GuiMgr.h"
#include "Gui/RpgGuis.h"
#include "Billiards/MiBilliardsBall_PlayGui.h"
#include "Billiards/BilliardsPlayer.h"
#include "Input/InputMgr.h"
#include "Billiards/MiniGameBilliards.h"
#include "Math/MathLibAdvance.h"
#include "Render/Camera.h"
#include "Render/Font.h"
#include "Render/MC_Misc.h"
#include "Render/RendDriver.h"
#include "Render/Shader.h"
#include "Packet/PacketMiniGame.h"
#include "Rpg/SyncGameInfo.h"
#include "Sound/ChannelSound.h"
#include "Sound/SoundManager.h"
#include "General/Pce.h"
#include "General/Option.h"



class CameraCtrlerBilliards:public CameraCtrlerTarget
{
public:
	CameraCtrlerBilliards();
	virtual void  Update();
	float m_dir;
	float m_pitch;
};

const char* BilliardsCmdToString(int enumeration)
{
	switch(enumeration)
	{
	case CMD_PrepareShoot:return "CMD_PrepareShoot";		
	case CMD_ShootStick  :return "CMD_ShootStick  ";		
//	case CMD_BallMove    :return "CMD_BallMove    ";		
	case CMD_BallIn      :return "CMD_BallIn      ";		
	case CMD_GameOver    :return "CMD_GameOver    ";		
	case CMD_Restart     :return "CMD_Restart     ";		     		       
	default             :return "CMD_unknow";
	}
	return "CMD_unknow";
}


CameraCtrlerBilliards::CameraCtrlerBilliards()
{
	m_dir = 0;
	m_pitch = -40;
}


void CameraCtrlerBilliards::Update()
{
	if (G_BilliardsGame==NULL)
	{
		//游戏结束
		return;
	}
		
	//CheckBoderRot(-HALFPI/8,HALFPI/3);
	CheckWheelDis(40,150);
	//static float keyMoveSpeed = 100;
	static float mouseRotSpeed = 10;

	Camera* camera = G_Camera;
	float time = G_Timer->GetStepTime();

	//camera->SetRot(0,-40,0);

	//rotate
	//#ifdef WIN32APP
	if (G_Mouse->IsButtonPressed(MOUSE_RIGHT))
	{
		vec2 p = G_Mouse->GetMove();
		p *= -1;
		float dir = p.x*time*mouseRotSpeed;
		//camera->AddRot(dir,pitch,0);
		m_dir += dir;
	
		if (m_dir>180)
		{
			m_dir = -180;
		}
		else if (m_dir<-180)
		{
			m_dir = -180;
		}
	}
	else if (G_Mouse->IsButtonPressed(MOUSE_LEFT))
	{
		vec2 p = G_Mouse->GetMove();
		p *= -1;
		float dir = p.x*time*mouseRotSpeed;
		//camera->AddRot(dir,pitch,0);
		m_dir += dir;

		if (m_dir>180)
		{
			m_dir = -180;
		}
		else if (m_dir<-180)
		{
			m_dir = 180;
		}

		float pitch = p.y*time*mouseRotSpeed;
		m_pitch += pitch;
		if (m_pitch<-80)
		{
			m_pitch = -80;
		}
		else if (m_pitch>-20)
		{
			m_pitch = -20;
		}
	}

	G_Camera->SetEuler(m_dir,m_pitch,0);
	//#endif
	//move
	vec3 tarPos = G_BilliardsGame->m_worldDeskCen;
	//tarPos = m_balls[WhiteBall].m_pos;
	SetTarPos(tarPos);
	camera->SetEyePos(tarPos - camera->GetHeadDir()*m_distToTar);

}

MiniGameBilliards *G_BilliardsGame;

MiniGameBilliards::MiniGameBilliards()
    : m_myRolePlayer(NULL)
{
    G_BilliardsGame = this;
	CmdEnumToString = BilliardsCmdToString;
    for (int i = 0; i < BallCount; i++)
    {
        m_movieBalls[i] = NULL;
    }
	for (int i = 0; i < 2; i++)
	{
		m_movieStick[i] = NULL;
	}

}

MiniGameBilliards::~MiniGameBilliards()
{
	G_BilliardsGame = NULL;
}

bool MiniGameBilliards::Start()
{
	m_myRolePlayer = NULL;
	m_oldFov = G_Option->m_defaultFov;
    if(!MiniGame::Start())
        return false;

	vec3 localDeskMin;
	vec3 localDeskMax;

    if (m_movieScene == NULL)
    {
        LoadConfig loader(LoadConfig::GenDonotReShrinkBound, true, true);
        m_movieScene = new RendSys::MovieClip;
        m_movieScene->LoadFromFile("data/minigame/billiards/desk.movie", &loader);
        m_movieScene->Advance();

		localDeskMin = m_movieScene->GetMovieClip("tag_min")->GetCollideSys().m_min;
		localDeskMax = m_movieScene->GetMovieClip("tag_max")->GetCollideSys().m_min;
        vec3 min = localDeskMin;
        vec3 max = localDeskMax;
        //坐标系转换后可能不对了
        if (localDeskMax.y < min.y)
        {
            localDeskMax.y = min.y;
        }
        if (localDeskMin.y > max.y)
        {
            localDeskMin.y = max.y;
        }
        if (localDeskMax.z < min.z)
        {
            localDeskMax.z = min.z;
        }
        if (localDeskMin.z > max.z)
        {
            localDeskMin.z = max.z;
        }

		Frame frame;
		frame.SetPos(m_startPos);
		m_movieScene->SetProgramFrame(&frame);
		m_movieScene->Advance();
    }

	for (int i = 0; i < 2; i++)
	{
		if (m_movieStick[i] == NULL)
		{
			char buf[128];
			sprintf(buf,"data/minigame/billiards/stick0%d.movie",i+1);
			LoadConfig loader(LoadConfig::GenDonotReShrinkBound, true, true);
			m_movieStick[i] = new RendSys::MovieClip;
			m_movieStick[i]->LoadFromFile(buf, &loader);
			m_movieStick[i]->SetFrustumSkipEnable(false, Recursive);
			m_movieStick[i]->Advance();
		}
	}
 
	m_movieBestPocket = m_movieScene->GetMovieClip("bestPocket");

    if (m_movieScene->IsLoadComplete() == false)
    {
        m_gameState = MS_End;
        return false;
    }

	//
	for (int b = 0; b < BallCount; b++)
	{
		m_balls[b].Reset();
		m_balls[b].m_id = (BallID)b;
	}

	m_blackInPacket = false;
	m_prepareTime = 0;

    m_balls[0].SetPos(vec3(0.0f, 0 , localDeskMin.z - localDeskMin.z / 3));

    //初始靠的太紧,碰撞不平衡
    m_balls[15].SetPos(vec3(7.0f, 0 , 27.0f));
    m_balls[1 ].SetPos(vec3(3.5f, 0 , 27.0f));
    m_balls[14].SetPos(vec3(0.0f, 0 , 27.0f));
    m_balls[13].SetPos(vec3(-3.5f, 0 , 27.0f));
    m_balls[2 ].SetPos(vec3(-7.0f, 0 , 27.0f));

    m_balls[3 ].SetPos(vec3(5.25, 0 , 23.5f));
    m_balls[12].SetPos(vec3(1.75, 0 , 23.5f));
    m_balls[4 ].SetPos(vec3(-1.75, 0 , 23.5f));
    m_balls[11].SetPos(vec3(-5.25, 0 , 23.5f));

    m_balls[10].SetPos(vec3(3.5f, 0 , 20.0f));
    m_balls[8].SetPos(vec3(0.0f, 0 , 20.0f));//black 在中间
    m_balls[5].SetPos(vec3(-3.5f, 0 , 20.0f));

    m_balls[6].SetPos(vec3(1.75, 0 , 16.5f));
    m_balls[7].SetPos(vec3(-1.75, 0 , 16.5f));

    m_balls[9].SetPos(vec3(0.0f, 0 , 13.0f));


	m_worldDeskMin = m_startPos + localDeskMin;
	m_worldDeskMax = m_startPos + localDeskMax;
	m_worldDeskCen = (m_worldDeskMin+m_worldDeskMax)*0.5f;
	m_simulateTime = 0;

	for (int b = 0; b < BallCount; b++)
	{
		m_balls[b].m_pos += m_worldDeskCen;
	}

	WhiteStartPos = m_balls[0].GetPos();

	PocketPos[0] = vec3(m_worldDeskMin.x,  m_worldDeskCen.y, m_worldDeskMax.z);
	PocketPos[1] = vec3(m_worldDeskMax.x,  m_worldDeskCen.y, m_worldDeskMax.z);
	PocketPos[2] = vec3(m_worldDeskMin.x,  m_worldDeskCen.y, m_worldDeskCen.z);
	PocketPos[3] = vec3(m_worldDeskMax.x,  m_worldDeskCen.y, m_worldDeskCen.z);
	PocketPos[4] = vec3(m_worldDeskMin.x,  m_worldDeskCen.y, m_worldDeskMin.z);
	PocketPos[5] = vec3(m_worldDeskMax.x,  m_worldDeskCen.y, m_worldDeskMin.z);
	

	//==================^_^==================^_^==================^_^==================^_^

    for (int i = 0; i < m_allPlayerNum; i++)
    {
		BilliardsPlayer* thePlayer = dynamic_cast<BilliardsPlayer*>(m_miniPlayer[i]);

		thePlayer->m_turn = i;

		Style* style = G_StyleMgr->GetStyle(thePlayer->GetPlayerInfo()->style);
		thePlayer->SetStyle(style);
		thePlayer->GetRenderCharacter()->SetScale(vec3(3.0f,3.0f,3.0f));
		thePlayer->GetRenderCharacter()->PlayAnim("billiards");//,"data/minigame/billiards");

		thePlayer->Start();
    }


	m_GameState = PrepareShooting;
    // First player in list starts the game
    dynamic_cast<BilliardsPlayer*>(m_miniPlayer[0])->m_shootNum = 1;
    m_curTurn = 0;

	//设置摄像机
	G_Camera->PopCtrler();
	CameraCtrlerBilliards* ctrl = new CameraCtrlerBilliards;
	ctrl->SetTarPos(m_worldDeskCen);
	G_Camera->PushCtrler(ctrl);
	//片头摄像机
	PushIntroCamera();

	//进入miniplaygui,(选人、选关卡都已在房间里进行完毕)。
	if(GetStyle()) G_GuiMgr->PushGui(GetStyle()->playGUI.c_str(),GL_DIALOG);

	G_Option->m_defaultFov = 60;
    return true;

}
MiniPlayer* MiniGameBilliards::CreatePlayer()
{
	return new BilliardsPlayer;
}

MiniPlayer* MiniGameBilliards::CreateRobot()
{
	return new BilliardsPlayerRobot;
}

MiniPlayer* MiniGameBilliards::CreateRole()
{
	m_myRolePlayer = new BilliardsPlayerRole;
	return m_myRolePlayer;
}
bool MiniGameBilliards::Stop()
{
	//
	G_Option->m_defaultFov = m_oldFov;

	G_Camera->PopCtrler();

    for (int i = 0; i < BallCount; i++)
    {
        SafeDelete(m_movieBalls[i]);
    }
	for (int i = 0; i < 2; i++)
	{
		SafeDelete(m_movieStick[i]);
		//SafeDelete(m_miniPlayer[i]);
	}

	G_GuiMgr->PopGui("MiBilliardsBall_PlayGui");

	
	{
		if (m_myRolePlayer && m_myRolePlayer->m_turn==m_curTurn)
		{
			G_GuiMgr->GetGui<Rpg_ResultDialog>()->ShowResult(true);
		}
		else
		{
			G_GuiMgr->GetGui<Rpg_ResultDialog>()->ShowResult(false);
		}
		G_GuiMgr->PushGui("Rpg_ResultDialog",GL_DIALOGBOTTOM); 
	}
	MiniGame::Stop();

    return true;
}

bool MiniGameBilliards::Render()
{
    m_movieScene->RendClip();

	//绘制玩家
	for (int i = 0; i < m_allPlayerNum; i++)
	{
		m_miniPlayer[i]->Render();
	}

    //绘制球  todo 调出高光效果 反射贴图 阴影贴图
    {
		mat4 mat;
        for (int p = 0; p < BallCount; p++)
        {
            G_RendDriver->PushMatrix();
			//G_RendDriver->LoadIdentity(); //ogl 没有单独的world 会把modelview一起清除
			m_balls[p].m_rot.ToMatrix(mat);
			mat.SetTranslate(m_balls[p].m_pos);
			G_RendDriver->MultMatrix(mat);

            m_movieBalls[p]->Advance();
            m_movieBalls[p]->RendClip();

            G_RendDriver->PopMatrix();
        }
    }

    if (m_GameState == PrepareShooting
		||m_GameState == Shooting)
    {
        //绘制球杆 todo billboard绘制更好看
        {
            G_RendDriver->PushMatrix();
            G_RendDriver->Translatef(m_balls[WhiteBall].m_pos.x, m_balls[WhiteBall].m_pos.y, m_balls[WhiteBall].m_pos.z);
            G_RendDriver->Rotatef(-GetTurnPlayer()->m_shootAngleRad * RAD2DEG + 90, 0.0f, 1.0f , 0.0f);
            G_RendDriver->Rotatef(-5.5f, 1.0f, 0.0f , 0.0f);
			//G_RendDriver->Rotatef(11.5f, 1.0f, 0.0f , 0.0f);
            G_RendDriver->Translatef(0.0f, 0.0f, GetTurnPlayer()->m_stickDistance*0.05f);
            m_movieStick[m_curTurn]->Advance();
            m_movieStick[m_curTurn]->RendClip();
            G_RendDriver->PopMatrix();
        }

		if(G_ShaderMgr)G_ShaderMgr->PushShader();

        //绘制指导线
        if (GetTurnPlayer()->m_useLazer == true
			&&( G_RendDriver->RendPassStepFlag==NormalPass|| G_RendDriver->RendPassStepFlag==PreRenderMrt)
			)
        {
			G_RendDriver->Color4f(1.0f, 1.0f, 1.0f, 0.5f);
			m_lazer->Bind();

			vec3  tar = GetBoundPosFromInner(m_balls[WhiteBall].m_pos,vec3(-cos(GetTurnPlayer()->m_shootAngleRad),0,-sin(GetTurnPlayer()->m_shootAngleRad)));

			float nearDist = 99999;
			vec3  whitePos = m_balls[WhiteBall].m_pos;
			vec3  dir = tar-whitePos; dir.Normalize();
			vec3  refDir;
			BilliardsBall* tarBall = NULL;
			Sphere sphere;
			sphere.r = m_balls[WhiteBall].m_radius*2;
			vec3 res;
			for (int p = 0; p < BallCount; p++)
			{
				if (p==WhiteBall)
				{
					continue;
				}
				sphere.c = m_balls[p].m_pos;
				float t = 0;
				if (IntersectRaySphere(whitePos,dir,sphere,t,res) && t<nearDist)
				{
					tarBall = &m_balls[p];
					nearDist = t;
					refDir = tarBall->m_pos - res;
					refDir.Normalize();
					tar = res;
					//
					//tar += refDir*m_balls[WhiteBall].m_radius;
				}
			}

			float tiley = (whitePos - tar).Length()/2;

			vec2 tex[6];
			tex[0] = vec2(0    ,tiley);
			tex[1] = vec2(0.25f ,tiley);
			tex[2] = vec2(0    ,0);
			tex[3] = vec2(0    ,0);
			tex[4] = vec2(0.25f,tiley);
			tex[5] = vec2(0.25f,0);
			//变色
			if (GetTurnPlayer()!=m_myRolePlayer)
			{
				for(int i=0;i<6;i++)
				{
					tex[i].x += 0.25f;
				}
			}
 
			//绘制指导线
			vec3 pos[6];
			vec3 left = dir.Cross(vec3(0,1,0));
			left.Normalize();
			left *= m_balls[WhiteBall].m_radius*0.8f;
			vec3 lev(0,-0.1f,0);
			pos[0] = whitePos - left + lev;
			pos[1] = whitePos + left + lev;
			pos[2] = tar - left + lev;
			pos[3] = tar - left + lev;
			pos[4] = whitePos + left + lev;
			pos[5] = tar + left + lev;
		
			G_RendDriver->RendTrigon(2,pos, tex);

			//绘制反射指导线1
			if (tarBall)
			{
				vec3 refTar = GetBoundPosFromInner(tar,refDir);
				float tiley = (tar- refTar).Length()/2;
				for(int i=0;i<6;i++)
				{
					if (tex[i].y>0)
					{
						tex[i].y = tiley;
					}
				}
				vec3 left = refDir.Cross(vec3(0,1,0));
				left.Normalize();
				left *= m_balls[WhiteBall].m_radius*0.8f;
				vec3 lev(0,-0.2f,0);
				pos[0] = tar - left + lev;
				pos[1] = tar + left + lev;
				pos[2] = refTar-left*0.5f + lev;
				pos[3] = refTar-left*0.5f + lev;
				pos[4] = tar + left + lev;
				pos[5] = refTar+left*0.5f + lev;
				G_RendDriver->RendTrigon(2,pos, tex);
			}

			//绘制反射指导线2
			//....

			//绘制碰撞点
			if (tarBall)
			{
				float tiley = 1;
				tex[0] = vec2(0.5f  ,tiley);
				tex[1] = vec2(0.75f ,tiley);
				tex[2] = vec2(0.5f  ,0);
				tex[3] = vec2(0.5f  ,0);
				tex[4] = vec2(0.75f ,tiley);
				tex[5] = vec2(0.75f ,0);
				vec3 left = refDir.Cross(vec3(0,1,0));
				left.Normalize();
				left *= m_balls[WhiteBall].m_radius;
				vec3 front = refDir*m_balls[WhiteBall].m_radius;
				pos[0] = tar - left - front;
				pos[1] = tar + left - front;
				pos[2] = tar - left + front;
				pos[3] = tar - left + front;
				pos[4] = tar + left - front;
				pos[5] = tar + left + front;
				G_RendDriver->RendTrigon(2,pos, tex);
			}
        }

		if(G_ShaderMgr)G_ShaderMgr->PopShader();

		//提示ai最佳袋子
		BilliardsPlayerRobot* robot = dynamic_cast<BilliardsPlayerRobot*>(GetTurnPlayer());
		Frame frame;
		if (robot && robot->m_workingWithAI)
		{
			frame.SetPos(PocketPos[robot->m_bestPocket] - m_startPos);
		}
		else
		{
			frame.SetPos(vec3());
		}
		m_movieBestPocket->SetProgramFrame(&frame);
    }

    return true;
}

void MiniGameBilliards::RenderUI()
{
	MiniGame::RenderUI();
	///
	G_RendDriver->BeginUI();
	G_RendDriver->Color4f(1,1,1,1);
	G_RendDriver->SetRenderStateEnable(RS_BLEND,true);
	G_RendDriver->BlendFunc(Blend_Filter);
	//G_ShaderMgr->PushShader();

    char shotBuffer[20];
    sprintf(shotBuffer, "%d", GetTurnPlayer()->m_shootNum);

    G_RendDriver->Color3f(1.0f, 1.0f, 1.0f);
    G_FontMgr->TextAtPos(vec2(80, 15), "Player: ");
    G_FontMgr->TextAtPos(vec2(80, 45), "Shots: ");

    G_RendDriver->Color3f(0.8f, 0.8f, 0.8f);
    G_FontMgr->TextAtPos(vec2(160, 15), GetTurnPlayer()->GetPlayerInfo()->playerName);
    G_FontMgr->TextAtPos(vec2(160, 45), shotBuffer);

    // 绘制玩家头像 move to ui

    //力度条
    if (m_GameState == PrepareShooting
		||m_GameState == Shooting)
    {
		RectF rect(130, 175, 180, 15);
		if (m_curTurn==1)
		{
			rect = RectF(G_Window->m_iWidth-130-180, 175, 180, 15);
		}
		RectF rect2(rect.x+1, rect.y+1, rect.width-2, rect.height-2);
        G_RendDriver->SetRenderStateEnable(RS_TEXTURE_2D, false);
        G_RendDriver->Color3f(1.0f, 1.0f, 0.0f);
        G_RendDriver->DrawRect(rect);

        G_RendDriver->Color3f(0.0f, 0.0f, 0.5f);
        G_RendDriver->DrawRect(rect2);

        G_RendDriver->Color3f(1.0f, 1.0f, 1.0f);
        G_RendDriver->SetRenderStateEnable(RS_TEXTURE_2D, true);
        m_powerBar->Bind();
        G_RendDriver->DrawTextureRect(RectF(rect2.x, rect2.y, GetTurnPlayer()->m_shootPower*rect2.width/100, rect2.height), RectF(0, 0, GetTurnPlayer()->m_shootPower / 100.0f, 1));
    }

    //已经进洞的球.
	if(GetTurnPlayer() && GetTurnPlayer()->m_teamSide>=0)
	{
		RectF rect(200+m_curTurn*300, 140, 32, 32);
		int startBall = (1-GetTurnPlayer()->m_teamSide)*8+1;
		for (int k = startBall; k < startBall+7; k++)
		{
			if (m_balls[k].m_inPocket == true)
			{
				G_RendDriver->Color3f(1.0f, 1.0f, 1.0f);
				m_ballIcons[k]->Bind();
				G_RendDriver->DrawTextureRect(rect);
				rect.x += 36.0f;
			}
		}
	}
	//G_ShaderMgr->PopShader();
}

bool MiniGameBilliards::Update()
{
    if (m_movieScene == NULL)
    {
        return false;
    }

    m_movieScene->Advance();

	switch(m_GameState)
	{
	case BallRolling:
		{
			bool moving = true;
			m_simulateTime += G_Timer->GetStepTimeLimited(); 

			//多个更新步模拟,避免单帧移动过大出现多次碰撞
			//减小模拟间隔 或使用连续性检测才能提高 robot的瞄准率

			float steptime = 0.002f;
			while(m_simulateTime>=steptime)
			{
				//steptime = min(0.01f,frametime);//最后一个剩余时间累积到下一帧,使得各方碰撞结果尽量几乎一致(更新时间步一致)
				m_simulateTime -= steptime;
				//球和球碰撞
				for (int b = 0; b < (BallCount - 1); b++)
				{
					for (int t =b+1; t < BallCount; t++)
					{
						DeflectBalls(m_balls[b], m_balls[t]);
					}
				}

				//球和洞碰撞
				for (int b = 0; b < BallCount; b++)
				{
					BilliardsBall& ball = m_balls[b];
					for (int p=0;p<6;p++)
					{
						if(ball.m_inPocket==false)
						{
							PocketInBall(m_balls[b], p);
						}	
					}
				}

				//球和桌面碰撞
				for (int b = 0; b < BallCount; b++)
				{
					BilliardsBall& ball = m_balls[b];
					if(ball.m_inPocket==false)
					{
						float minX = m_worldDeskMin.x;
						float minZ = m_worldDeskMin.z ;
						float maxX = m_worldDeskMax.x ;
						float maxZ = m_worldDeskMax.z ;

						float ballRadius = ball.m_radius;
						if (   ((ball.m_pos.x > maxX-ballRadius) && ball.m_speed.x >0 )
							|| ((ball.m_pos.x < minX+ballRadius) && ball.m_speed.x <0 )
							)
						{
							//速度摩擦力取反
							ball.m_speed *= 0.8f;
							ball.m_speed.x *= -1;
							ball.m_acc.x *= -1;
							m_sound->PlaySound__("data/sound/ui_click.wav");
						}

						if (   ((ball.m_pos.z > maxZ-ballRadius)  && ball.m_speed.z >0 )
							|| ((ball.m_pos.z < minZ+ballRadius)  && ball.m_speed.z <0 )
							)
						{
							//速度摩擦力取反
							ball.m_speed *= 0.8f;
							ball.m_speed.z *= -1;
							ball.m_acc.z *= -1;
							m_sound->PlaySound__("data/sound/ui_click.wav");
						}

						反射位置
						//if ((ball.m_pos.x > maxX-ballRadius) || (ball.m_pos.x < minX+ballRadius)
						//	||(ball.m_pos.z > maxZ-ballRadius) || (ball.m_pos.z < minZ+ballRadius))
						//{
						//	vec3 tar = GetBoundPosFromOut(ball.m_pos,ball.m_headDir*-1);
						//	//回退半径
						//	tar -= ball.m_headDir*ballRadius;
						//	//穿透力度
						//	//float interDis = (ball.m_pos - tar).Length()*0.8f;

						//	vec3 newDir = ball.m_speed;
						//	newDir.Normalize();

						//	//ball.m_pos = tar+newDir*interDis;

						//	m_sound->PlaySound__("data/sound/billiards/shoot.wav");
						//}
					}
				}
				moving = false;
				for (int p = 0; p < BallCount; p++)
				{
					if (m_balls[p].m_inPocket==false)
					{
						if (m_balls[p].UpdateMoving(steptime))
						{
							moving = true;
						}
					}
					m_balls[p].m_collided = false;
				}

				if (moving == false)
				{
					break;
				}
			}
			
			if (m_bHost== true
				//m_myRolePlayer&&
				//&&GetTurnPlayer()->m_turn == m_myRolePlayer->m_turn
				&& moving == false)
			{
				m_GameState = PrepareShooting;
				//滚动结束返还球
				if (m_balls[WhiteBall].m_inPocket)
				{
					m_balls[WhiteBall].SetPos(WhiteStartPos);
					m_balls[WhiteBall].m_speed = vec3();
					m_balls[WhiteBall].m_inPocket = false;
				}
				if (m_balls[BlackBall].m_inPocket && m_gameState != MS_End)
				{
					vec3 pos = m_worldDeskCen;
					//pos.z += m_localDeskMin.z*0.666f;
					m_balls[BlackBall].SetPos(pos);
					m_balls[BlackBall].m_speed = vec3();
					m_balls[BlackBall].m_inPocket = false;
				}

				//changeturn
				if (GetTurnPlayer()->m_shootNum<=0)
				{
					m_turnTime = 0;
					m_curTurn++;
					m_curTurn%=2;
					//换手后设置杆数
					if(GetTurnPlayer()->m_shootNum<=0)
					{
						GetTurnPlayer()->m_shootNum = 1;
					}
				}
				SendPrepareShooting();
			}
		}
		break;

	case PrepareShooting:
		m_prepareTime += G_Timer->GetStepTimeLimited(); 
		break;

	case Shooting:
		{
			//推杆动画
		}
		break;
	}
  
	m_turnTime += G_Timer->GetStepTimeLimited();

	for (int i = 0; i < m_allPlayerNum; i++)
	{
		m_miniPlayer[i]->Update();
	}

    return true;
}

bool MiniGameBilliards::Free()
{
	MiniGame::Free();
    return true;
}

bool MiniGameBilliards::KeepResource(bool once, int &circle, String &nextTip)
{

	char *ballTexFile[] =
	{
		"data/minigame/billiards/ball_white.png",
		"data/minigame/billiards/ball_yellow_solid.png",
		"data/minigame/billiards/ball_blue_solid.png",
		"data/minigame/billiards/ball_red_solid.png",
		"data/minigame/billiards/ball_purple_solid.png",
		"data/minigame/billiards/ball_orange_solid.png",
		"data/minigame/billiards/ball_green_solid.png",
		"data/minigame/billiards/ball_brown_solid.png",
		"data/minigame/billiards/ball_black.png",
		"data/minigame/billiards/ball_yellow_stripe.png",
		"data/minigame/billiards/ball_blue_stripe.png",
		"data/minigame/billiards/ball_red_stripe.png",
		"data/minigame/billiards/ball_purple_stripe.png",
		"data/minigame/billiards/ball_orange_stripe.png",
		"data/minigame/billiards/ball_green_stripe.png",
		"data/minigame/billiards/ball_brown_stripe.png",
	};

	for (int i = 0; i < BallCount; i++)
	{
		if (!m_movieBalls[i])
		{
			m_movieBalls[i] = new MovieClip;
			m_movieBalls[i]->LoadFromFile("data/minigame/billiards/ball.movie");
			m_movieBalls[i]->GetMovieClip("ball")->ReAttachTexture(ballTexFile[i]);
			m_movieBalls[i]->SetFrustumSkipEnable(false, Recursive);
		}
	}

	char *ballIconFiles[] =
	{
		"data/minigame/billiards/mini_white.png",
		"data/minigame/billiards/mini_yellow_solid.png",
		"data/minigame/billiards/mini_blue_solid.png",
		"data/minigame/billiards/mini_red_solid.png",
		"data/minigame/billiards/mini_purple_solid.png",
		"data/minigame/billiards/mini_orange_solid.png",
		"data/minigame/billiards/mini_green_solid.png",
		"data/minigame/billiards/mini_brown_solid.png",
		"data/minigame/billiards/mini_black.png",
		"data/minigame/billiards/mini_yellow_stripe.png",
		"data/minigame/billiards/mini_blue_stripe.png",
		"data/minigame/billiards/mini_red_stripe.png",
		"data/minigame/billiards/mini_purple_stripe.png",
		"data/minigame/billiards/mini_orange_stripe.png",
		"data/minigame/billiards/mini_green_stripe.png",
		"data/minigame/billiards/mini_brown_stripe.png",
	};
	for (int i = 0; i < BallCount; i++)
	{
		G_TextureMgr->AddTexture(m_ballIcons[i], ballIconFiles[i]);
	}

	G_TextureMgr->AddTexture(m_powerBar, "data/minigame/billiards/powerBar.png");
	G_TextureMgr->AddTexture(m_lazer, "data/minigame/billiards/lazer.png");

    return true;
}

void MiniGameBilliards::DeflectBalls(BilliardsBall &ballA, BilliardsBall &ballB)
{
	//减小模拟间隔 或使用连续性检测才能提高 robot的瞄准率
	if (ballA.m_inPocket 
		|| ballA.m_collided
		|| ballB.m_inPocket 
		|| ballB.m_collided
		) 
	{
		return;
	}

	vec3  posDif    = ballA.m_pos - ballB.m_pos;
	float posDifLen = posDif.Length();
	if (posDifLen >= (ballA.m_radius+ballB.m_radius)) 
	{
		if (ballA.m_collideBall == &ballB)
		{
			ballA.m_collideBall = NULL;
		}
		if (ballB.m_collideBall == &ballA)
		{
			ballB.m_collideBall = NULL;
		}
		//未接触
		return;
	} 

	vec3  speedDif = ballA.m_speed - ballB.m_speed;
	float speedLen = speedDif.Length();
	if (speedLen<_EPSILON)
	{
		return;
	}

    if (posDifLen <0.001f)
    {
        posDif = vec3(0, 0, 0.001f);
        posDifLen = 0.001f;
    }


	//ballA.m_pos += posDif * ((ballA.m_radius - posDifLen * 0.5f) / posDifLen);
	//ballB.m_pos -= posDif * ((ballB.m_radius - posDifLen * 0.5f) / posDifLen);

	//接触  回退到刚接触的时间点后再碰撞处理 更精确 保证robot的瞄准率
	float time = ((ballA.m_radius+ballB.m_radius)-posDifLen)/speedLen;
	ballA.m_pos -= ballA.m_speed * time;
	ballB.m_pos -= ballB.m_speed * time;
	posDif = ballA.m_pos - ballB.m_pos;


    ballA.m_collided = true;
    ballB.m_collided = true;

	if (ballA.m_collideBall != &ballB)
	{
		m_sound->PlaySound__("data/sound/ui_click.wav");
	}
	ballA.m_collideBall = &ballB;
	ballB.m_collideBall = &ballA;

    float impulse = 0.0f;
    float e       = 0.8f;

	//动量守恒
    impulse = ((-1) * (1.0f + e) * speedDif.Dot(posDif) ) / (posDif .Dot (posDif * (2.0f / ballA.m_mass)));

    ballA.m_speed += posDif*(impulse / ballA.m_mass);
    ballB.m_speed -= posDif*(impulse / ballB.m_mass);
}

//进洞
void MiniGameBilliards::PocketInBall(BilliardsBall& ball, int pocket)
{
	if (ball.m_inPocket)
	{
		return;
	}

	//
	vec3 dif = PocketPos[pocket]-ball.GetPos();
	dif.y = 0;
	if (dif.Length()<pocketRadius)
	{
		ball.m_inPocket = true;
	}

	//
	if (ball.m_inPocket)
	{
		BallID ballID = ball.m_id;
		ball.m_pos.y -= 5;
		ball.m_inPocket = true;
		//ball.m_rotAngleRad = 0;
		m_sound->PlaySound__("data/sound/billiards/sunk.wav");

		BilliardsPlayer* player = GetTurnPlayer();
		BilliardsPlayer* otherPlayer = dynamic_cast<BilliardsPlayer*>(m_miniPlayer[(m_curTurn + 1) % 2]);
		if ((ballID == WhiteBall))
		{
			//白球进洞返还
			if (m_blackInPacket == true)
			{
				//黑8同时进洞
				otherPlayer->m_shootNum = 2;
				player->m_shootNum = 0;
			}
		}
		else if (ballID == BlackBall)
		{
			//黑8球进洞
			int teamSide = player->m_teamSide;
			m_blackInPacket = true;
			if (teamSide != TeamSide_Null)
			{
				//7球全进,游戏结束
				if (InPocketNum(player->m_teamSide) == 7)
				{
					m_gameState = MS_End;
					//strcat(endGame, "Player wins");
				}
				else
				{
					//罚一杆 返还黑8
					otherPlayer->m_shootNum = 2;
					player->m_shootNum = 0;
				}
			}
		}
		else
		{
			if (player->m_teamSide == TeamSide_Null)
			{
				//第一次进球,决定花式
				player     ->m_teamSide = IsTeamSide(ballID,TeamSide_Solid)?TeamSide_Solid:TeamSide_Stripe;
				otherPlayer->m_teamSide = IsTeamSide(ballID,TeamSide_Solid)?TeamSide_Stripe:TeamSide_Solid;
			}


			if (m_blackInPacket == true)
			{
				//黑8同时进洞
				otherPlayer->m_shootNum = 2;
				player->m_shootNum = 0;
			}
			else
			{
				//自己花式进洞
				if (IsTeamSide(ballID,player->m_teamSide))
				{
					//加对方罚球可能是2
					if (player->m_shootNum<2)
					{
						player->m_shootNum++;
					}
				}
			}
		}
	}
}

int  MiniGameBilliards::InPocketNum(TeamSide side)
{
	int inPocketNum = 0;
	int startBall = (1-side)*8+1;
	for (int k = startBall; k < startBall+7; k++)
	{
		if (m_balls[k].m_inPocket == true)
			inPocketNum++;
	}
	return inPocketNum;
}

vec3 MiniGameBilliards::GetBoundPosFromInner(const vec3& pos,const vec3& dir)
{
	AABB aabb(m_worldDeskMin,m_worldDeskMax);
	vec3 newpos = pos+dir*1000;
	vec3 newdir = dir*-1;
	newdir.y = 0; //aabb.y 非常小
	float t;
	vec3 tar;
	IntersectRayAABB(newpos,newdir,aabb,t,tar);
	return tar;
}

vec3 MiniGameBilliards::GetBoundPosFromOut(const vec3& pos,const vec3& dir)
{
	vec3 tar;
	AABB aabb(m_worldDeskMin,m_worldDeskMax);
	float t;
	IntersectRayAABB(pos,dir,aabb,t,tar);
	return tar;
}



bool MiniGameBilliards::SendShootStick(float shootAngleRad,float shootPower)
{
	C2SPacketMiniGameCmd packet;
	packet.WriteHeader();
	packet.WriteValue(CMD_ShootStick);  
	packet.WriteValue(m_curTurn);
	packet.WriteValue(GetTurnPlayer()->m_shootNum);
	packet.WriteValue(shootAngleRad); 
	packet.WriteValue(shootPower); 
	G_MiniGame->SendPacketToOther(&packet);
	return true;
}

bool MiniGameBilliards::SendStickMove( float shootAngleRad,float stickDistance )
{
	C2SPacketMiniGameCmd packet;
	packet.WriteHeader();
	packet.WriteValue(CMD_StickMove);  
	//packet.WriteValue(m_curTurn);
	packet.WriteValue(shootAngleRad); 
	packet.WriteValue(stickDistance); 
	G_MiniGame->SendPacketToOther(&packet);
	return true;
}

bool MiniGameBilliards::SendPrepareShooting()
{
	m_prepareTime = 0;
	C2SPacketMiniGameCmd packet;
	packet.WriteHeader();
	packet.WriteValue(CMD_PrepareShoot); 
	packet.WriteValue(m_curTurn); //可能进洞 不一定换手
	packet.WriteValue(GetTurnPlayer()->m_shootNum); 
	for (int b=0;b<BallCount;b++)
	{
		packet.WriteValue(m_balls[b].m_inPocket); 
		packet.WriteValue(m_balls[b].m_pos); 
	}
	G_MiniGame->SendPacketToOther(&packet);
	return true;
}

bool MiniGameBilliards::ShootStick(float shootAngleRad,float shootPower)
{
	m_balls[WhiteBall].m_speed.x = shootPower * (cos(shootAngleRad)) * (-1.1f);
	m_balls[WhiteBall].m_speed.z = shootPower * (sin(shootAngleRad)) * (-1.1f);
	m_sound->PlaySound__("data/sound/billiards/shoot.wav");
	m_GameState = MiniGameBilliards::BallRolling;
	m_simulateTime = 0;
	m_blackInPacket = false;
	return true;
}

int  MiniGameBilliards::ProcessPacketCmd(PacketBase* packet)
{
	int cmd;
	packet->ReadValue(cmd);
	switch(cmd)
	{
	case CMD_PrepareShoot:
		m_prepareTime = 0;
		//不一定换手
		packet->ReadValue(m_curTurn);
		packet->ReadValue(GetTurnPlayer()->m_shootNum);
		m_turnTime = 0;

		for (int b=0;b<BallCount;b++)
		{
			packet->ReadValue(m_balls[b].m_inPocket); 
			packet->ReadValue(m_balls[b].m_pos); 
			m_balls[b].m_speed = vec3(0,0,0);
		}
		m_GameState = PrepareShooting;
		break;
	case CMD_StickMove:
		packet->ReadValue(GetTurnPlayer()->m_stickDistance);
		packet->ReadValue(GetTurnPlayer()->m_shootAngleRad);
		break;  

	//case CMD_BallIn:     //进球 
	case CMD_ShootStick://击球
		{
			对手
			//TetrisPlayer* otherPlayer = GetOtherPlayer();
			float shootAngleRad;
			float shootPower;
			packet->ReadValue(m_curTurn);
			packet->ReadValue(GetTurnPlayer()->m_shootNum); 
			packet->ReadValue(shootAngleRad); 
			packet->ReadValue(shootPower);
			ShootStick(shootAngleRad, shootPower);
		}
		break;
		//case CMD_GameOver:
		//	{
		//		int turn = 0;
		//		packet->ReadValue(turn);
		//		m_winnerTurn = turn;
		//		m_turnTime = 0;
		//		m_gameStatus = Resulting;

		//		char sound[256];
		//		if (m_winnerTurn == m_lordTurn)
		//		{
		//			sprintf(sound,"data/sound/poker/play_lord_win");
		//		}
		//		else
		//		{
		//			sprintf(sound,"data/sound/poker/play_farmer_win");
		//		}
		//		if (m_winnerTurn%2) //
		//			strcat(sound,"_femail.wav");
		//		else
		//			strcat(sound,".wav");
		//		m_players[m_winnerTurn]->m_sound->PlaySound__(sound);
		//	}
		//	break;
		//case CMD_Restart:
		//	Free();
		//	Start();
		//	break;
	}
	return 0;
}

bool MiniGameBilliards::IsTeamSide(BallID ball,TeamSide side)
{
	if (ball == WhiteBall || ball == BlackBall)
	{
		return false;
	}

	if (side==TeamSide_Null)
	{
		return true;//一球未进 决定花式
	}

	if (ball > BlackBall && side==TeamSide_Stripe)
	{
		return true;
	}
	else if (ball < BlackBall && side==TeamSide_Solid)
	{
		return true;
	}
	return false;
}

BilliardsPlayer* MiniGameBilliards::GetTurnPlayer()
{
	return dynamic_cast<BilliardsPlayer*>(m_miniPlayer[m_curTurn]);
}

玩家类:

//========================================================
//  @Date:     2016.05
//  @File:     SourceDemoClient/Billiards/BilliardsPlayer.h
//  @Brief:     MiniGameBilliards
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================
 

#ifndef  __BilliardsPlayer__H__
#define  __BilliardsPlayer__H__

#include "BilliardsBall.h"
#include "Rpg/MiniGame.h"
#include "Render/Texture.h"

enum TeamSide;

class BilliardsPlayer: public LogicCharacter,public MiniPlayer
{
public:
	BilliardsPlayer();
	virtual ~BilliardsPlayer();
	virtual bool Start();
	virtual void Update();
	virtual void Render();

	int      m_score;
	int      m_shootNum;
	TeamSide m_teamSide;
	int      m_turn;    //side 不等于turn	

	vec2     m_shootClickPos;
	float    m_shootPower;
	float    m_stickDistance;
	float    m_shootAngleRad;

	//指导线
	bool     m_useLazer;
};

class BilliardsPlayerRobot:public BilliardsPlayer
{
public:
	virtual bool Start();
	virtual void Update();
	void   SetWorkingWithAI(bool working);
	//寻找最佳目标球(白球-目标球-某个袋 距离和越小越好 夹角越小越好)
	void   FindBestGoal();
	bool   m_workingWithAI;	
	int    m_bestBall;
	int    m_bestPocket;
	float  m_bestPower;
	float  m_bestAngleRad;
		  
	float  m_dstPower;
	float  m_dstAngleRad;

	double m_thinkTime;
};

class BilliardsPlayerRole:public BilliardsPlayerRobot
{
public:
	virtual bool Start();
	virtual void Update();
};

#endif


//========================================================
//  @Date:     2016.05
//  @File:     SourceDemoClient/Billiards/BilliardsPlayer.cpp
//  @Brief:     MiniGameBilliards
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================

#include "General/Pch.h"
#include "General/General.h"
#include "General/StringUtil.h"
#include "General/Timer.h"
#include "Gui/GuiMgr.h"
#include "Input/InputMgr.h"
#include "Billiards/MiniGameBilliards.h"
#include "Billiards/MiBilliardsBall_PlayGui.h"
#include "Billiards/BilliardsPlayer.h"
#include "Render/Camera.h"
#include "Render/Font.h"
#include "Render/MC_Misc.h"
#include "Render/RendDriver.h"
#include "Rpg/SyncGameInfo.h"
#include "Sound/ChannelSound.h"
#include "General/Pce.h"
#include "Math/MathLibAdvance.h"

//
BilliardsPlayer::BilliardsPlayer()
:m_useLazer(true)
{
}

BilliardsPlayer::~BilliardsPlayer()
{
}

bool BilliardsPlayer::Start()
{
	m_shootNum = 0;
	m_teamSide = TeamSide_Null;
    m_shootAngleRad = _PI + _PI / 2.0f;
	m_shootPower = 0;
	m_score = 0;
	m_stickDistance = 0;
	return false;
}

void BilliardsPlayer::Update()
{
	if (G_BilliardsGame->m_curTurn == m_turn)
	{
		switch(G_BilliardsGame->m_GameState)
		{
		case MiniGameBilliards::BallRolling:
			{
				//PlayAnim("stand");
			}
			break;
		case MiniGameBilliards::PrepareShooting:
			{
				mat4 rot;
				rot.FromRotateY(-(m_shootAngleRad + HALFPI));
				m_heading = rot* vec3(0, 0, 1);
				//	m_stickDistance 逆向运动学手部动画

				m_pos = G_BilliardsGame->m_balls[WhiteBall].m_pos - m_heading *30;
				m_pos.y -= 15;
				LogicCharacter::Update();

				//todo 
				AABB aabb(G_BilliardsGame->m_worldDeskMin,G_BilliardsGame->m_worldDeskMax);
				if(TestPointAABB(m_pos,aabb))
				{
					//PlayAnim("billiards_sit");//坐姿
				}
				else
				{
					//PlayAnim("billiards"); //站姿
				}
				//手部动画有待改进,可以加入逆向运动学解算或直接计算预制关键帧。 根据stickDistance设置播放头即可
			}
			break;
		}
	}
}

void BilliardsPlayer::Render()
{
	if (G_BilliardsGame->m_curTurn == m_turn)
	{
		LogicCharacter::Render();
	}
}

bool BilliardsPlayerRobot::Start()
{
	BilliardsPlayer::Start();
	m_workingWithAI = true;

	m_bestBall = 0;
	m_bestPocket = 0;
	m_bestPower = 0;

	return false;
}

void BilliardsPlayerRobot::Update()
{
	BilliardsPlayer::Update();
	if (G_BilliardsGame->m_curTurn == m_turn)
	{
		switch(G_BilliardsGame->m_GameState)
		{
		case MiniGameBilliards::BallRolling:
			{
				//球和球碰撞
				m_shootAngleRad = 0;
				m_shootPower = 0;
				m_bestPower = 0;
				m_bestAngleRad = 0;
			}
			break;

#define MaxThinkTime 3
		case MiniGameBilliards::PrepareShooting:
			//右键 开始拉杆
			
			if (G_BilliardsGame->m_prepareTime > MaxThinkTime)
			{
				//抬起开始推杆
				//最终决定误差 0.5°
				float amp = 1;
				m_shootAngleRad = m_bestAngleRad + 0.5f*DEG2RAD *(RandRange(-amp,amp));
				m_shootPower    = m_bestPower    + 10 *(RandRange(-amp,amp));
				m_shootAngleRad = fmod(m_shootAngleRad+TWOPI,TWOPI); 
				Clamp(m_shootPower, 0,100);
				G_BilliardsGame->m_GameState = MiniGameBilliards::Shooting;
			}
			else
			{
				//拉杆
				if (m_bestPower<_EPSILON)
				{
					//寻找最佳目标球
					FindBestGoal();

					//
					vec3  packetPos = G_BilliardsGame->PocketPos[m_bestPocket];
					vec3  hitPos = G_BilliardsGame->m_balls[m_bestBall].m_pos;
					vec3  difPD    = hitPos - packetPos;
					difPD.Normalize();
					//碰撞时白球所在的位置
					hitPos += difPD*(G_BilliardsGame->m_balls[WhiteBall].m_radius+G_BilliardsGame->m_balls[m_bestBall].m_radius);

					vec3  whitePos = G_BilliardsGame->m_balls[WhiteBall].m_pos;
					vec3  difWD = hitPos - whitePos;
					difWD.Normalize();
					m_bestAngleRad = _PI + atan2(difWD.z, difWD.x);

					//最佳力量 距离越远 需要力度越大
					m_bestPower = (packetPos-whitePos).Length();
					//撞击越偏(拐角越大)需要力度越大
					float dot = abs(difPD.Dot(difWD));
					dot = Max(dot,0.2f);
					m_bestPower /= dot;
					//计算白球反弹位置 到达某一区域 对下次击球更有利
					//...

					//m_bestAngleRad = fmod(m_bestAngleRad+TWOPI,TWOPI); 
					Clamp(m_shootPower, 0,100);
					m_thinkTime   = 999;
				}

				m_thinkTime+= G_Timer->GetStepTimeLimited();
				if (m_thinkTime>0.3f)
				{
					m_thinkTime = 0;
					float amp = 1-G_BilliardsGame->m_prepareTime/MaxThinkTime;
					amp = amp * amp;
					m_dstAngleRad = m_bestAngleRad + 45*DEG2RAD *(RandRange(-amp,amp));
					m_dstPower    = m_bestPower    + 50 *(RandRange(-amp,amp));
					m_dstAngleRad = fmod(m_dstAngleRad+TWOPI,TWOPI); 
					Clamp(m_dstPower, 0,100);
				}

				float difAngRad = m_dstAngleRad-m_shootAngleRad;
				bool  addRot    = (difAngRad>0&&difAngRad<_PI)||(difAngRad<0&&difAngRad<-_PI);
				m_shootAngleRad += (addRot ? 1:-1)*G_Timer->GetStepTimeLimited()*100*DEG2RAD; //旋转速度100°/s
				m_shootPower    += ((m_dstPower-m_shootPower)>0 ? 1:-1)*G_Timer->GetStepTimeLimited()*50;

				m_shootAngleRad = fmod(m_shootAngleRad+TWOPI,TWOPI); 
				Clamp(m_shootPower, 0,100);

				m_stickDistance = m_shootPower;

				//同步瞄准
				if (G_Timer->GetCurrentFrame()%10==0)
				{
					G_BilliardsGame->SendShootStick(m_shootAngleRad,m_shootPower);
				}
			}
			break;

		case MiniGameBilliards::Shooting:
			{
				//推杆动画
				if (m_stickDistance >= 3.0f)
				{
					m_stickDistance -= m_stickDistance / 2.0f;
				}
				else
				{
					m_shootNum -= 1;
					G_BilliardsGame->ShootStick(m_shootAngleRad,m_shootPower);
					G_BilliardsGame->SendShootStick(m_shootAngleRad,m_shootPower);
					m_shootPower = 3.0f;
				}
			}
			break;
		}
	}
}

//寻找最佳目标球(白球-目标球-某个袋 距离和越小越好 夹角越小越好)
void BilliardsPlayerRobot::FindBestGoal()
{
	//白球-目标球-某个袋 之间不能有干扰球

	//各球到白球的距离
	float ballRadX2 = G_BilliardsGame->m_balls[WhiteBall].m_radius*2.1f;
	vec3  whitePos = G_BilliardsGame->m_balls[WhiteBall].m_pos;
	float dis[BallCount];
	for (int p = 0; p < BallCount; p++)
	{
		dis[p] = (G_BilliardsGame->m_balls[p].m_pos - whitePos).Length();
	}

	//简单寻找最近的球
	m_bestBall = BlackBall;
	//if (G_BilliardsGame->InPocketNum(m_teamSide)<6)
	{
		float bestScore = 0;
		vec3  difPW;
		for (int p = 0; p < BallCount; p++)
		{
			BilliardsBall& pBall = G_BilliardsGame->m_balls[p];
			if(    pBall.m_inPocket==false //未进
				&& G_BilliardsGame->IsTeamSide((BallID)p,m_teamSide)
				)
			{
				//距离越近分越高
				float scoreWD = 1.0f/dis[p];

				bool visibleWD = true;//白球-目标球 之间无干扰球
				difPW = pBall.m_pos - whitePos;	
				for (int b = 0; b < BallCount; b++)
				{
					if (  dis[b] < dis[p]//距离更近才可能干扰
						&& b!=WhiteBall
						&& b!=p
					)
					{
						BilliardsBall& bBall = G_BilliardsGame->m_balls[b];
						float area = (bBall.m_pos - whitePos).Cross(difPW).Length();
						float h = area*2/dis[p];
						if(-ballRadX2<h && h<ballRadX2)
						{
							visibleWD = false;
							break;
						}
					}				
				}
				if (visibleWD)//无遮挡时加分
				{
					scoreWD += 10;
				}

				//寻找最佳的洞
				float scoreDP = 0;
				//int bestPocket = 0;
				//for (int i=0;i<6;i++)
				//{
				//	bool visibleDP = true;//目标球-袋 之间无干扰球
				//}
		
				float score = scoreWD + scoreDP;
				
				if (score > bestScore)
				{
					bestScore = score;
					m_bestBall = p;
					//m_bestPocket = bestPocket;
				}
			}
		}
	}

	//若没有不被干扰球 考虑二次碰撞击球 三次碰撞击球 判断难度(误差大小)
	//...

	//寻找最佳的洞
	m_bestPocket = 0;
	float maxDot = 0;
	vec3  pocketPos;
	vec3  hitPos = G_BilliardsGame->m_balls[m_bestBall].m_pos;
	vec3  dif = hitPos - whitePos;
	dif.Normalize();
	vec3  dif2;
	for (int i=0;i<6;i++)
	{
		pocketPos = G_BilliardsGame->PocketPos[i];
		dif2 = pocketPos-hitPos;
		dif2.Normalize();
		float dot = dif.Dot(dif2);
		if (dot>maxDot)
		{
			maxDot = dot;
			m_bestPocket = i;
		}
	}
}

void BilliardsPlayerRobot::SetWorkingWithAI(bool working)
{
	m_workingWithAI = working;
}

bool BilliardsPlayerRole::Start()
{
	BilliardsPlayerRobot::Start();
	m_workingWithAI = false;
	return false;
}

void BilliardsPlayerRole::Update()
{
	if (m_workingWithAI==true)
	{
		return BilliardsPlayerRobot::Update();
	}
	BilliardsPlayer::Update();
	if (G_BilliardsGame->m_curTurn == m_turn)
	{
		switch(G_BilliardsGame->m_GameState)
		{
		case MiniGameBilliards::BallRolling:
			{
				//球和球碰撞
			}
			break;

		case MiniGameBilliards::PrepareShooting:
			{
				vec3 head = G_Camera->GetHeadDir();
				m_shootAngleRad = _PI + atan2(head.z, head.x);

				//右键 开始拉杆
				if (G_Mouse->IsButtonDowning(MOUSE_RIGHT))
				{
					//G_BilliardsGame->m_GameState = MiniGameBilliards::Shooting;
					m_shootClickPos = G_Mouse->GetMousePos();
				}
				else if (G_Mouse->IsButtonUping(MOUSE_RIGHT))
				{
					//抬起开始推杆
					G_BilliardsGame->m_GameState = MiniGameBilliards::Shooting;
				}
				else if (G_Mouse->IsButtonPressed(MOUSE_RIGHT))
				{
					//拉杆
					m_shootPower = (G_Mouse->GetMousePos().y - m_shootClickPos.y) / 3;
					if (m_shootPower < 0)
					{
						m_shootPower = 0;
					}
					else if (m_shootPower > 100)
					{
						m_shootPower = 100;
					}
					m_stickDistance = m_shootPower;
					//同步瞄准
					if (G_Mouse->GetMove().LengthSq()>0)
					{
						G_BilliardsGame->SendShootStick(m_shootAngleRad,m_shootPower);
					}
				}
			}
			break;

		case MiniGameBilliards::Shooting:
			{
				//推杆动画
				if (m_stickDistance >= 3.0f)
				{
					m_stickDistance -= m_stickDistance / 2.0f;
				}
				else
				{
					m_shootNum -= 1;
					G_BilliardsGame->ShootStick(m_shootAngleRad,m_shootPower);
					G_BilliardsGame->SendShootStick(m_shootAngleRad,m_shootPower);
					m_shootPower = 3.0f;
				}
			}
			break;
		}
	}
}