A星算法和双向A星
实现效果
A星寻路
双向A星
介绍
基于VS2017 MFC Direct3D9实现的A星寻路
什么也不说就是干!
用mfc创建单文档框架,不选文档视图结构,生成代码,程序逻辑封装到AStarGame里面。主要在CChildView里面操作。大体结构就是AStarApp里面有一个主框架CMainFrame主框架里面有一个CChildView,视图里面有一个游戏AStarGame,游戏里面有一个GameObjectManager,游戏管理有一个AStarMap,地图里面是主要的寻路逻辑。
AStarApp和CMainFrame介绍
一行代码没改
CChildView介绍
这个里面声明AStarGame变量,这里有一个关键点,创建窗口的时候窗口大小是0,无法创建设备需要将创建设备的逻辑放到OnSize函数里面,第一次进入这个函数是创建的时候,第二次是主框架布局的时候,这个时候的大小是确定的。
void CChildView::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
// TODO: 在此处添加消息处理程序代码
if (cx > 0 && cy > 0)
{
if (!m_pGame->GameInited())
{
m_pGame->Init(m_hWnd);
}
}
}
视图类主要是将MFC框架的事件传入到游戏逻辑里面,以驱动游戏逻辑。视图更新是用定时器驱动游戏渲染的。
// ChildView.cpp: CChildView 类的实现
//
#include "pch.h"
#include "framework.h"
#include "AStarApp.h"
#include "ChildView.h"
#include "AStarViewport.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CChildView
CChildView::CChildView()
{
m_pGame = new CAStarGame();
}
CChildView::~CChildView()
{
AStarDelete(m_pGame);
}
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_TIMER()
ON_WM_SIZE()
ON_WM_KEYDOWN()
END_MESSAGE_MAP()
// CChildView 消息处理程序
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), nullptr);
return TRUE;
}
D3DXVECTOR2 CChildView::GetViewportPoint(CPoint point)
{
RECT rt;
GetClientRect(&rt);
float width = (float)(rt.right - rt.left);
float height = (float)(rt.bottom - rt.top);
LPDIRECT3DSURFACE9 pSurface = nullptr;
CAStarGame::GetDevice()->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pSurface);
D3DSURFACE_DESC desc = {
};
pSurface->GetDesc(&desc);
return D3DXVECTOR2(point.x * desc.Width/width, point.y * desc.Height/height);
}
void CChildView::OnPaint()
{
CPaintDC dc(this); // 用于绘制的设备上下文
// TODO: 在此处添加消息处理程序代码
m_pGame->Render();
// 不要为绘制消息而调用 CWnd::OnPaint()
}
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
SetTimer(1, 10, nullptr);
return 0;
}
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_pGame->OnLButtonDown(GetViewportPoint(point));
CWnd::OnLButtonDown(nFlags, point);
}
void CChildView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_pGame->OnRButtonDown(GetViewportPoint(point));
CWnd::OnRButtonDown(nFlags, point);
}
void CChildView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent)
{
case 1:
m_pGame->Render();
break;
default:
break;
}
CWnd::OnTimer(nIDEvent);
}
void CChildView::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
// TODO: 在此处添加消息处理程序代码
if (cx > 0 && cy > 0)
{
if (!m_pGame->GameInited())
{
m_pGame->Init(m_hWnd);
}
}
}
void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_pGame->OnKeyDown(nChar, nRepCnt, nFlags);
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
主要监听鼠标左右键和键盘数字1事件,其他的倒是没有什么。
CAStarGame介绍
游戏管理摄像机、视口和游戏物体管理器以及Direct3D对象。至于为什么要用Direct3D,就是纯粹为了练习。
#pragma once
class GameObjectManager;
class CAStarCamera;
class CAStarViewport;
class CAStarGame
{
public:
CAStarGame();
virtual ~CAStarGame();
public:
static CAStarGame* GetInstance();
static LPDIRECT3DDEVICE9 GetDevice();
static CAStarCamera* GetCamera();
static CAStarViewport* GetViewport();
protected:
static CAStarGame* m_sInstance;
public:
bool Init(HWND hWnd);
void Render();
void Release();
void OnLButtonDown(const D3DXVECTOR2& point);
void OnRButtonDown(const D3DXVECTOR2& point);
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
public:
const bool GameInited() {
return m_bInit; }
protected:
LPDIRECT3D9 m_pD3D;
LPDIRECT3DDEVICE9 m_pDevice;
GameObjectManager* m_pGameObjectManager;
CAStarCamera* m_pCamera;
CAStarViewport* m_pViewport;
protected:
bool m_bInit;
};
游戏初始化
创建d3d对象和根据窗口句柄创建设备,根据客户区大小创建正交摄像机和视口,是正交摄像机。
bool CAStarGame::Init(HWND hWnd)
{
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!m_pD3D)
return false;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.BackBufferCount = 1;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
if (FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &m_pDevice)))
return false;
m_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
m_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
m_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
if (!m_pGameObjectManager->Init())
return false;
if (!m_pCamera->Init())
return false;
RECT rt;
GetClientRect(hWnd, &rt);
DWORD width = rt.right - rt.left;
DWORD height = rt.bottom - rt.top;
float radio = width * 1.0f / height;
m_pCamera->SetProjMatrixOrth(10.0f * radio, 10.0f, 0.1f, 100.0f);
m_pCamera->SetViewMatrix(D3DXVECTOR3(0.0f, 0.0f, -10.0f), D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3(0.0f, 1.0f, 0.0f));
m_pCamera->Active();
if (!m_pViewport->Init())
return false;
m_pViewport->SetParam(0,0, width , height, 0.0f, 1.0f);
m_pViewport->Active();
m_bInit = true;
return true;
}
游戏逻辑继续向游戏对象传
主要是键盘和鼠标事件。
void CAStarGame::OnLButtonDown(const D3DXVECTOR2& point)
{
D3DXVECTOR3 vecIn(point.x, point.y, 1.0f);
D3DXVECTOR3 vecOut;
m_pViewport->InvTransform(vecIn, vecOut);
m_pGameObjectManager->OnLButtonDown(vecOut);
}
void CAStarGame::OnRButtonDown(const D3DXVECTOR2& point)
{
D3DXVECTOR3 vecIn(point.x, point.y, 1.0f);
D3DXVECTOR3 vecOut;
m_pViewport->InvTransform(vecIn, vecOut);
m_pGameObjectManager->OnRButtonDown(vecOut);
}
void CAStarGame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
m_pGameObjectManager->OnKeyDown(nChar, nRepCnt, nFlags);
}
视口InvTransform
鼠标点击转换到游戏世界很关键。两种办法,注释是一种,直接调用DX的API是另外一种
void CAStarViewport::InvTransform(const D3DXVECTOR3& vecIn, D3DXVECTOR3& vecOut)
{
/*D3DXMATRIX matViewportScale;
D3DXMatrixIdentity(&matViewportScale);
matViewportScale._11 = 1.0f * m_Viewport.Width / 2.0f;
matViewportScale._22 = -1.0f * m_Viewport.Height / 2.0f;
matViewportScale._33 = m_Viewport.MaxZ - m_Viewport.MinZ;
matViewportScale._41 = m_Viewport.X * 1.0f + m_Viewport.Width / 2.0f;
matViewportScale._42 = m_Viewport.Y * 1.0f + m_Viewport.Height / 2.0f;
matViewportScale._43 = m_Viewport.MinZ;
D3DXMATRIX invViewportScale;
D3DXMatrixInverse(&invViewportScale, NULL, &matViewportScale);
D3DXVECTOR3 vecPort;
D3DXVec3TransformCoord(&vecPort, &vecIn, &invViewportScale);
D3DXMATRIX matProj = CAStarGame::GetCamera()->GetProjMatrix();
D3DXMATRIX invMatProj;
D3DXMatrixInverse(&invMatProj, NULL, &matProj);
D3DXVECTOR3 vecProj;
D3DXVec3TransformCoord(&vecProj, &vecPort, &invMatProj);
D3DXMATRIX matView = CAStarGame::GetCamera()->GetViewMatrix();
D3DXMATRIX invMatView;
D3DXMatrixInverse(&invMatView, NULL, &matView);
D3DXVec3TransformCoord(&vecOut, &vecProj, &invMatView);*/
D3DXMATRIX matProj = CAStarGame::GetCamera()->GetProjMatrix();
D3DXMATRIX matView = CAStarGame::GetCamera()->GetViewMatrix();
D3DXVec3Unproject(&vecOut, &vecIn, &m_Viewport,
&matProj,