Windows Mobile DirectDraw游戏示例(译文)
By S.F.
本文链接 https://www.kyfws.com/news/windows-mobile-directdraw-game-example/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 20 分钟阅读 - 9873 个词 阅读量 0Windows Mobile DirectDraw游戏示例(译文)
原文地址:https://www.codeproject.com/Articles/38584/Windows-Mobile-DirectDraw-Game-Example
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
重新制作了一个旧游戏,作为DirectDraw的简单演示.
下载安装程序-200 KB下载源代码-1.88 MB下载SDK 1.1.0-86.9 KB
介绍
我为其他人制作了这个游戏,作为一个样例.但是看了一下,我认为代码可能包含对其他一些人有用的信息,因此我决定共享它. 在对旧游戏的重制中,我利用DirectDraw API来处理几种Windows Mobile分辨率的图形渲染.我利用一种称为MIP MAPS的旧技术来解决一些人遇到的分辨率问题,同时将游戏逻辑与分辨率的差异隔离开来.相同的二进制文件将在Windows Mobile Standard/SmartPhone和Windows Mobile Professional/Pocket PC上运行,而无需进行任何修改. 该游戏还利用Samsung Windows Mobile SDK来利用其手机提供的一些用户交互功能,包括加速计,滚轮键,触觉反馈和通知LED.我将在三个部分中讨论该程序:游戏逻辑,DirectDraw和Samsung Windows Mobile SDK.
安装
要运行游戏,您必须安装两个出租车.一个出租车包含Samsung Mobile SDK文件,另一个出租车包含游戏.即使您没有使用三星设备,也将需要安装三星SDK驾驶室.
下载Samsung Windows Mobile SDK
要编译此游戏,您将需要下载Samsung Windows Mobile SDK(即使您未在Samsung设备上运行它).可从其工具和SDK页面免费获得SDK,并且只需要快速且简单的注册.
HTC加速度计支持
该游戏不支持HTC设备中的加速度计.没有HTC加速度计进行测试对我来说是不可能拥有或使用这种设备的.但是,如果您可以使用此类设备并了解如何使用其加速度计,则需要更新的代码量很小.
游戏逻辑
该游戏的目的是清除玻璃球的区域.只要三种颜色相互接触,球就会被擦除.玩家可以将新的球体射击到游戏场中以尝试匹配其颜色.但是,如果场的集合到达比赛区域的底部,则游戏结束.
Windows Mobile设备具有各种形状和大小,这是一款很大程度上取决于运动场形状的游戏.我决定将运动场变成正方形,然后将其放置在设备屏幕上.如果设备的屏幕比屏幕高,则屏幕最左侧和最右侧的区域将不使用.如果屏幕的高度大于宽度的高度,则屏幕顶部和底部的区域将未被使用.
游戏本身的逻辑是完全不知道屏幕的实际大小的. play区域(由
Play
Area类表示为by
)使用浮点数表示世界上所有对象的位置.世界上所有物品的位置都用名为PointF的结构定义.与POINT结构类似,PointF具有两个名为x和y的字段.虽然POINT结构在这些字段中具有整数数据,但PointF结构使用浮点数.在该游戏的世界范围内,只有" y"个" x"手可以对物体(球体)进行一个" t"形.
宝珠类
游戏中的彩色球体由" Orb"类表示.所有球体的大小都相同,因此我使用了名为Orb
:: radius的静态字段来设置其直径(当前设置为32.0).球具有位置,速度,颜色,并且可以设置标志以指示其从运动场中掉落.掉落在地上的宝珠不能与其他宝珠互动.因此,出于交互的所有目的,被设置为坠落的球体消失了,无非是短暂的回忆过往.
Orb类具有一个名为IsTcouching(Orb
*)的成员方法.给定对另一个球的引用,如果球在接触距离之内,则此函数将返回" true",否则返回" false".如前所述,下落的球无法与其他任何物体相互作用.因此,即使两个球重叠,如果它们中的至少一个被设置为下降,那么此函数将始终返回" false".球体的位置由其中心点的坐标定义.我们可以使用勾股定理来获得两个球体的两个中心点之间的距离.如果p1是一个球体的位置而p2是另一个球体的位置,则两个球体在sqrt((p1.x-p2.x)^ 2 +(p1) .y-p2
.y)^ 2)<=Orb
:: radius.移动设备的处理器往往较弱,因此只要可以简化计算,就应该做到.在这种情况下,我可以通过对两边都平方来简化计算,得出(p1
.x-p2
.x)^ 2 +(p1
.y-p2
.y)^ 2> =( Orb
:: radius)^ 2. Orb :: IsTouching方法使用后一个不等式来确定两个球是否在接触.
要移动球,需要将其速度设置为零以外的其他值. Orb :: SetVelocity(float x,float y)方法用于执行此操作.速度以每秒的坐标位移表示(请记住,我们的世界大小为512 x 512).一旦设置了球的速度,就会定期调用Orb :: Step(float timeDelta)方法,以在timeDelta中指定的时间单位数目之后计算球的新位置.如果一个球体开始通过世界坐标之外,那么它将朝相反的方向反弹(如果它离开了世界的左侧或右侧),或者会完全停止(如果它撞到了球体的顶部)世界).球降落时,它只有一次将球移动到世界低端的速度.掉落的球体在被删除并重新获得记忆之前,可以通过世界下部的世界坐标.
#pragma once
#include "stdafx.h"
#include "common.h"
#include <list>
using namespace std;
#define ORB_RADIUS 32
#define ORB_TOUCHING_DISTANCE2 ((ORB_RADIUS*2)*(ORB_RADIUS*2))
class Orb
{
private:
bool falling;
static const int touchingDistanceSquared =
ORB_TOUCHING_DISTANCE2;
static RECTF BoundingBox;
OrbColor color;
PointF position;
PointF velocity;
public:
static const int radius = ORB_RADIUS;
Orb(OrbColor color, float x, float y);
void SetVelocity(float x, float y);
void GetVelocity(PointF* vel);
void GetPosition(PointF* pos);
OrbColor GetColor();
bool IsTouching(Orb* otherOrb);
void Step(float timeUnit);
bool IsFalling() {return falling;};
void SetFalling();
bool IsMoving() {return (velocity.x!=0)||(velocity.y!=0);}
PointF GetPosition() { return position; }
};
游乐区课程
游戏区域由PlayArea''类表示.它包含游戏中所有正在使用的球体的集合,以及对游戏控制器状态的解耦知识(稍后会对此进行更多介绍). PlayArea类也跟踪玩家希望射击下一个球体的角度. PlayArea类提供有关球体组的一些有价值的信息.如果玩家成功地制作了一组匹配的球体,则" PlayArea"类将检测到该匹配及其中涉及的所有球体.此类物品会检测到那些因去除与之相配的球而受影响且悬浮在空中的球,而该球未与其他固定的球连接,因此可以将其设置为下降状态. PlayArea :: LoadLeveL从文本文件加载一个球体排列,并为我们设置一个关卡.并且,
PlayArea`` :: Clear方法可用于从运动场中移除所有球体.有关游戏状态的大多数信息都包含在此类中.我利用标准模板列表类来包含球体.如果您以前从未使用过标准模板库,强烈建议您熟悉它,因为知道它可能会提高生产率.
#pragma once
#include "stdafx.h"
#include <list>
#include "common.h"
#include "Orb.h"
#include "GameController.h"
using namespace std;
#define PLAY_AREA_SIZE 512
class PlayArea
{
private:
list<orb*> orbList;
list<orb*> orbDestructionList;
GameController* gameController;
Orb* loadedOrb;
Orb* movingOrb;
int GetColorsInPlay();
OrbColor nextOrbColor;
public:
PlayArea(GameController* controller)
{orbList;gameController=controller;loadedOrb=NULL;movingOrb = NULL;}
PlayArea(int playAreaSize);
Orb* PlaceOrb(OrbColor color, float x, float y);
void LoadOrb(OrbColor color);
void SetCannonAngle(float angle);
void IncrementAngle(float incrementAmount);
void LoadLevel(LPWSTR levelData);
list<orb*>* GetOrbList() { return &orbList; }
Orb* GetLoadedOrb() { return loadedOrb; }
Orb* GetMovingOrb() { return movingOrb; }
void FireOrb(float angle);
void StopMovingOrb();
REQUIRESDELETE MyOrbList* GetIntersectingOrbList(Orb*);
OrbColor GetNextOrbColor() { return nextOrbColor; }
void SelectNextColor();
REQUIRESDELETE list<Orb*>* PlayArea::GetMatchingSet(Orb* targetOrb);
list<Orb*>* GetSuspendedOrbs();
//void DestroyOrbs(OrbLink* orbList);
void DestroyOrb(Orb* target);
void Clear();
int GetOrbCount();
Orb* GetLowestOrb();
};
GameLogic类
GameLogic类的接口是该程序中使用的最简单的接口.该类公开了两种方法:GameLogic :: LoadLevel打开一个文本文件,并将其内容传递给PlayArea :: LoadLevel和GameLogic
:: Step执行游戏处理的交互.每次调用GameLogic` :: Step时,都会移动球体,检查匹配球体或游戏结束的条件,并对这些条件做出适当的响应.
围绕运动球与静止球接触发生的逻辑需要更多说明.因为球的运动是离散的而不是连续的,所以当一个球与另一个球接触时,两个球可能会稍微重叠.发生这种情况时,球将向后移动到球仍然接触但不重叠的位置.这个过程的解释很简单,但是实现起来却稍微复杂一些.实施如下所示.
current->GetPosition(&orb2Position);
movingOrb->GetPosition(&orb1Position);
float dx = orb2Position.x-orb1Position.x;
float dy = orb2Position.y-orb1Position.y;
//Get the distance by which we need to move the orb backwards
float targetDistance = Orb::radius*2-(float)sqrt((double)(dx*dx+dy*dy));
if(targetDistance!=0.0)
{
//get the (linear) speed of the ball
movingOrb->GetVelocity(&velocity);
float speed = (float)sqrt((velocity.x*velocity.x)+
(velocity.y*velocity.y));
//calculate the needed time over which the reverse velocity should be applied
float adjTime = targetDistance/speed;
//reverse the velocity
movingOrb->SetVelocity(-velocity.x,-velocity.y);
//Step Backwards
movingOrb->Step(adjTime);
//undo the reverse velocity
movingOrb->SetVelocity(velocity.x,velocity.y);
}
GameController类
游戏控制器类作为从任何实际输入设备可能提取的抽象级别存在.某些正在玩此游戏的人可能会使用方向键,滚轮键(例如Samsung Blackjack II SGH-i617上的那个),加速度计或屏幕来提供部分输入.我创建了一个类,该类打包了游戏需要知道的信息,而不是将游戏的逻辑与这些输入设备中每一个的可能状态混淆在一起.用户必须提供的输入的简化视图是是否按下了操作按钮(发射球)以及选择将要射击球的"角度".因此," GameController"类具有两条信息:" angle"和" actionButtonPressed".在加速度计的情况下,用户可以通过倾斜设备直接选择"角度".在使用键盘或控制盘的情况下,用户逐渐选择一个"角度". GameController :: SetAngle允许直接设置" angle",而GameController
:: IncrementAngle递增(或递减)输入" angle".对于动作按钮,GameController
:: SetActiobButtonPressed将设置用于通知游戏逻辑该动作按钮被按下的标志.用GameController
:: ClearActionButton清除标志由游戏决定.当游戏需要读取控制器状态时,GameController
:: IsActionButtonPressed返回动作按钮标志的状态,GameController
:: GetAngleDeg返回角度(以度为单位),而GameController
:: GetAngleRad返回以弧度表示的"角度".
#pragma once
#include "Stdafx.h"
#include "common.h"
#define MAX_CONTROLLER_ANGLE 80.0f
// The GameController represents the information that we
// need to get from the player (as opposed to a physical
// controller). New methods of interaction added to the
// game should manipulate this class. This layer of
// separation allows for easier adaptation to new control
// methods reducing/removing the need to modify game logic
// for a new controller type.
class GameController
{
private:
float angle ;
bool actionButtonPressed;
void CheckAngleBoundaries();
public:
GameController();
float IncrementAngle(float amount);
float GetAngle();
float GetAngleRad();
float SetAngle(float newAngle);
void PressActionButton();
void ClearActionButton();
bool IsActionButtonPressed();
};
PlayAreaRenderer类和DisplayList类
从游戏逻辑的角度来看," PlayAreaRenderer"类可处理游戏对象的绘制.实际上,此类创建一个待办事项列表,其中包含渲染游戏场景必须执行的所有操作的列表.必须执行的项目列表存储在DisplayList
类中.与该程序中使用的大多数列表不同,DisplayList类包含一个静态大小的列表.在此类中使用动态列表将对性能和内存碎片产生负面影响,因为它每秒重新生成几次(大约30-40,具体取决于设备的性能).
#pragma once
#include "stdafx.h"
#include "common.h"
#include "PlayArea.h"
#include "Orb.h"
#include "DisplayList.h"
#include "OrbRenderer.h"
class PlayAreaRenderer
{
private:
OrbRenderer* orbRenderer;
PlayArea* playArea;
DisplayList* displayList;
GameController* gameController;
public:
PlayAreaRenderer(OrbRenderer * or, PlayArea* pa,
GameController *gc, DisplayList* dl)
{
playArea = pa;
orbRenderer = or;
displayList = dl;
gameController = gc;
}
void Render();
};
#pragma once
#include "stdafx.h"
#include "common.h"
struct DisplayItem
{
RECT itemSource;
RECT itemDestination;
};
class DisplayList
{
private:
DisplayItem itemList[100];
int itemCount;
public:
DisplayList(){itemCount=0;}
void Clear() {itemCount=0;}
void AddItem(RECT* itemSource, RECT* itemDestination);
void Render(IDirectDrawSurface* spriteSource,
IDirectDrawSurface* destinationSurface, POINT offset);
};
GameEventNotification类别
GameEventNotification类用作将通知发送回用户的去耦层.每当发生以下四个事件之一时,就会调用该函数:触发一个球,停止一个球,确定该球的下一个颜色或用户成功进行颜色匹配.如果我想做任何事情来通知用户这些操作之一,则将在此类中调用通知的实现.每当球被发射或停止时,此处显示的代码都会生成触觉反馈(有关触觉反馈的更多信息,请参见下面的"三星Windows Mobile SDK"部分),或更改通知LED的颜色以传达下一个球体颜色.
#pragma once
#include "Stdafx.h"
#include "common.h"
#include "SmiHaptics.h"
class GameEventFeedback
{
SmiHapticsNote launchNote[1];
bool hasNotificationLed;
bool canBlink;
bool hasHapticFeedback;
SMI_HAPTICS_HANDLE hapticsHandle;
public:
GameEventFeedback();
~GameEventFeedback();
void OrbFired();
void NextColorDecided(OrbColor);
void OrbStopped();
void ColorMatchMade();
};
DirectDraw和渲染游戏
我之前提到过,该游戏可以在具有不同分辨率的多个Windows Mobile设备上进行渲染.我通过称为mipmaps的技术处理了多种分辨率的渲染. Mip是拉丁词multim im parvo的缩写,意思是"很多东西在一个小地方". mipmaps的使用可以追溯到20多年以前,但在当今世界中仍然很重要.许多3D系统都使用mipmap来保存纹理细节,而Microsoft的DeepZoom技术则广泛使用它.要解释什么是mipmap,请看下面的球体图像.
该图像包含了我在游戏中使用的几种分辨率的球体.当将球体吸引到屏幕上时,将使用与当前需求最接近的一组球体.名为OrbRenderer的类包含用于根据给定需求选择球的逻辑.该类的构造函数包含用于基于分辨率进行决策的唯一逻辑.游戏其余大部分内容与分辨率无关.类的构造函数根据球体半径,逻辑区域(世界单位)中的游戏区域的大小以及将在其上绘制的屏幕表面的物理大小来执行计算.
OrbRenderer::OrbRenderer(
float logicalPlayAreaSize,
float logicalOrbRadius,
float physicalPlayAreaSize,
DisplayList* displayList)
{
this->targetDisplayList=displayList;
logicalSize = logicalPlayAreaSize;
this->scalingFactor = physicalPlayAreaSize/logicalPlayAreaSize ;
destinationOrbSize.x=destinationOrbSize.y=
(int)logicalOrbRadius*2.0f*scalingFactor;
destinationOrbRadius = destinationOrbSize.x/2;
if(destinationOrbSize.x>=48)
{
sourceOrigin.x=0;
sourceOrigin.y=0;
orbDisplacement.x=64;
orbDisplacement.y=0;
orbImageSize.x=orbImageSize.y=64;
}
else if (destinationOrbSize.x>=24)
{
sourceOrigin.x=0;
sourceOrigin.y=64;
orbDisplacement.x=32;
orbDisplacement.y=0;
orbImageSize.x=orbImageSize.y=32;
}
else if (destinationOrbSize.x>=12)
{
sourceOrigin.x=0;
sourceOrigin.y=96;
orbDisplacement.x=16;
orbDisplacement.y=0;
orbImageSize.x=orbImageSize.y=16;
}
else
{
sourceOrigin.x=0;
sourceOrigin.y=112;
orbDisplacement.x=8;
orbDisplacement.y=0;
orbImageSize.x=orbImageSize.y=8;
}
}
使用DirectDraw
创建一个DirectDraw对象
DirectDraw通过COM接口公开其功能.与任何COM接口一样,您必须记住在不再需要该接口时将其释放.否则会导致内存泄漏,因此您将听到我在本文中重申发布COM接口的重要性.用于访问大多数DirectDraw功能的接口名称为[IDirectDraw". IDirectDraw实现的实例是通过函数DirectDrawCreate获取的/en-us/library/aa919095.aspx).这是" DirectDrawCreate“的调用签名:
HRESULT WINAPI DirectDrawCreate(
GUID FAR* lpGUID,
LPDIRECTDRAW FAR* lplpDD,
IUnknown FAR* pUnkOuter
);
对于我们的用法,仅第二个参数很重要.其他两个参数应设置为” NULL".该函数将检索实现" IDirectDraw"的对象,并将其地址分配给第二个参数中指定的接口指针.
设置DirectDraw模式(合作级别)
DirectDraw程序可以以两种模式或协作级别之一运行:互斥或正常.选择的模式将影响程序与系统其余部分的交互方式(因此称为"合作级别").在独占模式下,您拥有整个显示器,并且能够使用页面翻转.在普通模式下,您无权翻页,并且必须牢记绘制位置.给定这两条信息,排他模式似乎是更有吸引力的模式.但是,通过独占模式获得的自由带来了更多的责任.在独占模式下,通知(例如来电)无法通过.因此,您必须随时注意通知并做出相应的响应.对于本文的大部分内容,我们将以普通模式工作.通过IDirectDraw :: SetCooperationLevel设置合作级别.此方法的调用签名如下:
HRESULT SetCooperativeLevel(
HWND hWnd,
DWORD dwFlags
);
第一个参数是您应用程序顶层窗口的句柄.第二参数是指示要设置的模式的标志.要使用独占模式,请传递标志DDSCL_FULLSCREEN |. DDSCL_EXCLUSIVE
一起.要使用普通模式,请传递标志" DDSCL_NORMAL".
DirectDraw初始化后,我们几乎准备开始绘制.但是,我们需要一个可以绘制的对象.对于DirectDraw API,作为绘制操作目标的对象称为表面.表面实现IDirectDraw表面接口.表面可以在屏幕上或屏幕外.屏幕上的表面与可视显示存储器相关联.屏幕外表面可以存在于设备视频存储器的不可见部分或主存储器中.方法IDirectDraw:: CreateSurface将创建IDirectDraw为我们提供的表面对象.其调用签名如下:
HRESULT CreateSurface(
LPDDSURFACEDESC lpDDSurfaceDesc,
LPDIRECTDRAWSURFACE FAR* lplpDDSurface,
IUnknown FAR* pUnkOuter
);
第一个参数包含有关要创建的曲面的信息.第二个参数是输出参数.指向新创建的曲面的指针将传递到第二个参数中引用的变量中.第三个参数可以为" null".第一个参数值得更多解释.究竟是什么" DDSURFACEDESC"?
DirectX中的许多函数将其参数堆叠在结构中. IDirectDraw :: CreateSurface也不例外. DDSURFACEDESC参数打包有关将要创建的曲面的信息.根据我们要创建的表面类型,某些字段是可选的.要知道我们已经填充了哪些字段,而我们没有填充哪些字段,必须为名为dwFlags的成员设置标志,以指示我们已经填充了哪些字段.必须将另一个名为dwSize的成员函数设置为DDSURFACEDESC结构的sizeof
.在DirectDraw的未来版本中,DDSURFACEDESC的大小可能会增加,因此DirectDraw使用此参数来知道大小我们正在使用的DDSURFACEDESC结构的概述.要在可见视频存储器上创建表面,唯一必须设置的参数是ddsCaps成员. ddsCaps成员的类型为DDSCAPS. Windows Mobile的DDSCAPS实现只有一个名为dwCaps的字段.该字段接受标志.需要标记DDSCAPS_PRIMARYSURFACE才能在可见的视频内存中创建表面.
DirectDraw飞剪机
在以普通协作模式运行的典型程序中,程序的无限制绘制是不希望的. DirectDraw提供了一种机制,用于限制我们通过DirectDraw剪切器在屏幕上(或任何其他DirectDraw表面)绘制的区域. DirectDraw剪切器实现IDirectDrawClipper接口.这些对象基于GDI区域.因此,您可以提供无法进行绘图的矩形区域的列表.对于我们的程序,我们将使用一种更简单的方法来创建剪切器.若要仅允许在客户端窗口占用的区域中进行绘制,可以将窗口的句柄传递给IDirectDrawClipper:: SetHWnd.剪辑器本身是通过IDirectDraw :: CreateClipper创建的.
发条
如果您熟悉GDI编程,那么Blting的概念并不是什么新鲜事物. BLT代表位块传输,或将一个内存块中的数据传输到另一个.大多数现代图形API都将具有此功能的等效功能.对于在此程序中执行的DirectDraw钝化,我将黑色设置为代表透明度的颜色.在我的原始Mipmap图像中,黑色像素将导致没有像素在那里渲染.如果要对此进行测试,请在我的球体图像的中心内打黑圈,然后您会在游戏中看到甜甜圈而不是球体.
三星Windows Mobile SDK
三星的Windows Mobile SDK是我遇到的OEM的第一个(也是唯一一个)实例,该OEM支持开发人员访问其设备的特定功能.在其他设备上,如果您想访问Windows Mobile API未公开的功能,则必须使用一些逆向工程和黑客手段(或等待其他人这样做),然后才能使用它.如果在将来的设备修订版中以其他方式实现了该功能,则也必须考虑到这一点. 使用三星的Windows Mobile SDK,每个三星设备中功能实现方式的详细信息都通过一致的界面抽象出来.他们的SDK可让您查询功能是否存在,如果存在,则可以获取有关其可以做什么和不能做什么的更多详细信息.有关SDK的更多信息,请访问Samsung Mobile Innovator网站.
车轮钥匙
我将SDK用于非常特定的用途.在三星二十一点上,有一个慢速拨盘式界面,称为Wheelkey.它位于显示器下方.对于此游戏,滚轮键是控制游戏的自然方法.如果您监视与滚轮键盘相关的Windows消息,它们将以" VK_UP"和" VK_DOWN"按键的形式出现.使用Samsung Mobile SDK,我可以区分" VK_UP"或" VK_DOWN"消息,该消息是由于某些人按下方向键而产生的.
case VK_UP:
{
UINT scanCode = (lParam & 0x00FF0000) >> 16;
if(TRUE == SmiKeyMsgIsFromWheelKey(wParam,scanCode))
{
// Message is from wheelkey rotation
g_gameController->IncrementAngle(-5);
}
}
break;
case VK_DOWN:
{
UINT scanCode = (lParam & 0x00FF0000) >> 16;
if(TRUE == SmiKeyMsgIsFromWheelKey(wParam,scanCode))
{
//Message is from Wheelkey Rotation
g_gameController->IncrementAngle(5);
}
}
break;
加速度计
三星设备中的加速度计可以异步或同步读取.由于该游戏具有离散性,因此同步读取对我来说很好用.只需两行代码即可查看游戏是否在带有加速度计的三星硬件上运行.
SmiAccelerometerCapabilities accelCaps;
g_hasAccelerometer = (SMI_SUCCESS == SmiAccelerometerGetCapabilities(&accelCaps));
如果存在三星加速度计,则" g_hasAccelerometer"将设置为等于" true".在游戏循环中,读取加速度计矢量.加速度计总是返回指向物理向下的任何方向的向量.如果用户更改了设备的方向,那么我也必须考虑到这一点.可以通过读取注册表项来找到用户设备的方向.键返回值0\90\180或270,以指示设备屏幕旋转了多少度.
DWORD GetScreenOrientation()
{
DWORD retVal ;
HKEY hKey;
DWORD dataSize = sizeof(DWORD);
RegOpenKeyEx(HKEY_LOCAL_MACHINE,TEXT("System\\GDI\\Rotation"),NULL,NULL,&hKey);
RegQueryValueEx(hKey,TEXT("Angle"),NULL,NULL,(LPBYTE)&retVal, &dataSize);
RegCloseKey(hKey);
return retVal;
}
该值用于重新定向从加速度计读取的向量.有了合适的值后,我将进行一些触发以找出设备旋转了多少度.在带有加速度计的设备上玩球时,球将始终尝试朝着物理指向的方向射击.
if(g_hasAccelerometer)
{
SmiAccelerometerVector vect,rotatedVect;
if(SMI_SUCCESS==SmiAccelerometerGetVector(&vect))
{
switch(screenOrientation)
{
case 0:
rotatedVect = vect;
break;
case 90:
rotatedVect.x=-vect.y;
rotatedVect.y=vect.x;
rotatedVect.z=vect.z;
break;
case 180:
rotatedVect.x=-vect.x;
rotatedVect.y=-vect.y;
rotatedVect.z=vect.z;
break;
case 270:
rotatedVect.x=vect.y;
rotatedVect.y=-vect.x;
rotatedVect.z=vect.z;
break;
}
float angle;
float absAngle;
float distance;
if(rotatedVect.y==0)
angle = 0;
else
{
angle = -atan(rotatedVect.x / rotatedVect.y);
angle = (angle/(2*3.141592))*360.0;
}
g_gameController->SetAngle(angle);
}
}
通知LED
某些三星设备具有通知LED,它们可以以七种不同的颜色点亮:红色,黄色,绿色,蓝色,紫色和白色.如果在这样的设备上运行该游戏,则通知LED会指示下一个球的颜色. LED将亮起最多4秒钟,以指示下一种颜色.通知LED可以通过SmiLedTurnOn
激活.该函数需要一个" COLORREF"变量来指示颜色,并使用其他一些参数来指示闪烁模式和应打开LED的时间长度.我使用GameEventFeedback :: NextColorDecided
方法中的代码:
void GameEventFeedback::NextColorDecided(OrbColor color)
{
SmiLedAttributes attrib;
switch(color)
{
case Orb_Red: attrib.color=RGB(0xFF,0x00,0x00); break;
case Orb_Yellow: attrib.color=RGB(0xFF,0xFF,0x00); break;
case Orb_Green: attrib.color=RGB(0x00,0xFF,0x00); break;
case Orb_Blue: attrib.color=RGB(0x00,0x00,0xFF); break;
case Orb_Teal: attrib.color=RGB(0x00,0xFF,0xFF); break;
case Orb_Purple: attrib.color=RGB(0xFF,0x00,0xFF); break;
case Orb_White: attrib.color=RGB(0xFF,0xFF,0xFF); break;
default: attrib.color=0;break;
};
attrib.onTime=750;
attrib.offTime=250;
attrib.duration=4000;
attrib.pattern= SMI_LED_PATTERN_SOLID;
SmiLedTurnOn(SMI_LED_ID_NOTIFICATION,&attrib);
}
触觉反馈
对于具有触觉反馈功能的三星设备,当一个球被发射或碰到其他球时,您会收到反馈.触觉反馈通过定义触觉注释数组来调用.触觉说明包含有关振动强度和形状的信息.就我的目的而言,一个便笺就足够了,因此我创建了一个包含一个元素的数组来包含便笺数据.该数组在GameEventFeedback类中.
SmiHapticsNote launchNote[1]; //Haptic note array of one element
GameEventFeedback`类的构造函数检测是否存在触觉反馈硬件.如果存在硬件,则会根据硬件的功能填充注释.
GameEventFeedback::GameEventFeedback()
{
hapticsHandle=NULL;
SmiLedCapabilities ledCaps;
SmiHapticsCapabilities hapticCaps;
SMI_RESULT result;
result = SmiLedGetCapabilities(SMI_LED_ID_NOTIFICATION,&ledCaps);
if(result==SMI_SUCCESS)
{
hasNotificationLed = true;
canBlink = ledCaps.blinkIsSupported;
}
result = SmiHapticsGetCapabilities(&hapticCaps);
if(SMI_SUCCESS==result)
{
//Haptic feedback hardware is present
if(SMI_SUCCESS==SmiHapticsOpen(&hapticsHandle))
{
//Open a handle to the haptic feedback
//device and populate the note data
//according to the hardware's capability
hasHapticFeedback = true;
launchNote[0].duration=max(hapticCaps.minPeriod,400);
launchNote[0].magnitude=255;
if(hapticCaps.startEndMagIsSupported)
{
launchNote[0].startingMagnitude=64;
launchNote[0].endingMagnitude=255;
}
launchNote[0].period=max(hapticCaps.minPeriod,0.);
if(hapticCaps.noteStyleIsSupported)
launchNote[0].style=SMI_HAPTICS_NOTE_STYLE_STRONG;
}
else
{
hasHapticFeedback = false;
}
}
}
一旦创建了音符并打开了硬件的句柄,便可以通过一次调用SmiHapticsPlayNotes来播放音符序列.这是在GameEventFeedback :: OrbFired和GameEventFeedback :: OrbStopped方法中完成的.
void GameEventFeedback::OrbFired()
{
SmiHapticsPlayNotes(hapticsHandle,1,launchNote,FALSE,NULL);
}
void GameEventFeedback::OrbStopped()
{
SmiHapticsPlayNotes(hapticsHandle,1,launchNote,FALSE,NULL);
}
光电鼠标
一些三星设备(例如Epix)具有既可以用作鼠标垫又可以用作方向盘的输入设备.用户可以更改垫的模式.在鼠标模式下,它在游戏中不会发挥多大作用.因此,游戏将自动检测到此输入设备的存在,并在程序启动时将其切换到光标模式.
//if an optical mouse is present then
//ensure it is in navigation mode.
SmiOpticalMouseCapabilities mouseCaps;
//Get the optical mouse capabilities
SmiOpticalMouseGetCapabilities(&mouseCaps);
// if a mouse pad is present
if(mouseCaps.multiOperationModeIsSupported)
{
//ensure the mouse pad is in navidation mode
SmiOpticalMouseSetMode(SMI_OPTICAL_MOUSE_MODE_NAVIGATION);
}
其他资讯
更快的Windows消息处理结构
开发人员尝试在Windows Mobile上编写其第一个图形密集型应用程序时,常见的错误是使用Visual Studio为您创建的默认消息处理结构.默认消息处理结构是为桌面应用程序设计的.在大多数此类应用程序中,除非发生某些情况(收到电子邮件,用户按下按钮等),否则该应用程序将什么也不做.默认消息处理结构是针对这些类型的方案而设计的.试图在图形密集型程序或任何视觉状态恒定的程序中使用该结构,将导致性能低于标准.在使用了不适当的结构之后,我听说开发人员得出这样的结论:他们之所以表现欠佳,是由于Windows中的某些功能不足,或者是由于" WM_PAINT"消息没有足够高的优先级,而实际上发生的是错误的模式是应用于他们的程序. 接下来是一个更理想的消息处理结构.在以下结构中,程序将能够连续更新其显示.尽管您可以通过此消息处理循环获得更流畅的视频,但它也带来了更高的功耗.当游戏失去焦点时,我具有符合旧消息处理结构的代码,在该结构中,线程被阻塞直到有消息要处理为止.如果用户让游戏在后台运行,则可以防止游戏耗尽电池电量.
while(keepRunning)
{
if(g_bHasFocus)
{
if(PeekMessage(&msg,NULL,0,0,TRUE))
{
if(msg.message==WM_QUIT)
keepRunning=false;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
//Execute Game Logic Here
//Relinquish control of the processor but
//stay on queue of threads ready to run
//within the thread scheduler
Sleep(0);
}
}
else
{
if(GetMessage(&msg,NULL,0,0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
关卡文件的格式
我只给游戏一个关卡.关卡布局的格式在纯文本文件中.在每一行上,单词" ORB"后跟球体的X和Y坐标,然后是指示球体颜色的数字(有关数字到颜色的映射,请参见" OrbColor"枚举).每行以分号结尾.如果您决定创建新的关卡文件或编辑我提供的关卡文件,请注意以下几点:
- 如果
收盘时
如前所述,我将这段代码作为示例.我不打算更新它.但是,这段代码的一部分来自我对各种Windows Mobile Graphic API所做的一些研究.一旦完成研究,我希望能举例说明如何使用所有图形API.我略过了DirectDraw,但没有在本文中进行详细介绍,但是我整理了一份更详细的指南.到目前为止,该指南的DirectDraw部分大约有15页. Direct3D,GDI/GDI +,DirectShow和OpenGL ES都是本指南的一部分.我计划在未来几周内将每个部分作为自己的文章发布.
历史
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
WinMobile5 VS2008 C++ WinMobile Visual-Studio DirectX Dev Intermediate Advanced VC9.0 WinMobile6 新闻 翻译