UEFI开发探索95 – 弹跳小游戏

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

昨天闲暇的时候,把Tim Lewis的迷宫游戏编译了一下,自娱自乐在UEFI下玩了一把。

奇怪的是,CSDN竟然给我发了“入选《游戏领域内容榜》第27名”,如下图:

图1 CSDN奇怪的评选

嗯,虽然从理论上来说,这篇博客也算写了个游戏,可我主要写的是UEFI程序啊。除了这篇博客的名字外,在关键字里我也没有设置“游戏”字样。

实在搞不懂CSDN的评判算法。

不管了,昨天看Tim Lewis的代码中,还有一些游戏,今天想再编译试试。

1 Bounce游戏

在早期的Windows XP中,有一个让人印象深刻的屏保。桌面上一个小球,在屏幕上来回撞,运行的过程中画出各种轨迹。

Bounce就是用来实现类似功能的UEFI程序。

不过,直接编译后,发现无法运行。看了下代码,图像是通过类似HII的方式来组织的。

不大习惯这种方式,我还是决定自己把程序修改一下。

1.1 游戏架构

从功能上来说,要实现的比较简单:
1) 图像显示(包括指定位置的图像显示);
2) 遇到障碍物的动作处理(主要是屏幕四周)。

这是个粗糙的架构,在此基础上,可以扩展多个图像碰撞后的处理,实现类似以前的屏保的效果。

实际上,UEFI开发探索系列博客中所开发的图像处理函数,完全可以直接使用。

为了方便,我也是在之前的代码基础上进行移植的。基础工程是以前的MyGuiFrame,修改了其中与图像处理相关Picture.c,以及添加了一个文件处理函数,位于FileRW.c中。

1.2 移植和编写代码

主要的编写步骤如下。

1)编写文件读取到内存的函数

修改原来通过GUID获取文件的方法,直接读取bmp图像到内存。文件处理的函数,放在了FileRW.c中,如下所示:

//robin 增加一个处理图像文件的加载函数
EFI_STATUS
LoadFile(
  IN CONST CHAR8    *FilePath,
  OUT VOID          **File,
  OUT UINT32        *FileSize
)
{
  FILE *f = fopen(FilePath, "rb");
  if (f == NULL) {
    return EFI_NOT_FOUND;
  }

  fseek(f, 0, SEEK_END);
  *FileSize = ftell(f);
  fseek(f, 0, SEEK_SET);  //same as rewind(f);

  *File = malloc(*FileSize);
  if (*File == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (fread(*File, *FileSize, 1, f) < 1) {
    return EFI_DEVICE_ERROR;
  }
  fclose(f);
  return EFI_SUCCESS;
}

2)编写图像显示函数

图像显示的函数包括DisplayImage()和DisplayImageAt(),内容如下:

EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mBlt;
UINTN mHeight;
UINTN mWidth;
int DisplayImageAt (UINTN X, UINTN Y) 
{
  gGraphicsOutput->Blt (
                     gGraphicsOutput,
                     mBlt,
                     EfiBltBufferToVideo,
                     0,
                     0,
                     X,
                     Y,
                     mWidth,
                     mHeight,
                     mWidth * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                     );
  return 0;
}


EFI_STATUS
DisplayImage (
  IN CONST CHAR8    *BmpFilePath
  ) 
{
  EFI_STATUS Status;
  UINT8 *ImageData;
  UINTN ImageSize;
  UINTN BltSize;
  UINTN CoordinateX;
  UINTN CoordinateY;
  //robin 修改原有代码,将图像文件加载
  Status = LoadFile(BmpFilePath, (VOID **)&ImageData, &ImageSize);
  if (EFI_ERROR(Status)) {             //print error messages
    return FALSE;
  }
  
  BltSize = 0;
  Status = ConvertBmpToGopBlt (
            ImageData,
            ImageSize,
            (VOID **) &mBlt,
            &BltSize,
            &mHeight,
            &mWidth
            );
  if (EFI_ERROR (Status)) {
    free (ImageData);
  }
  
  CoordinateX = (gGraphicsOutput->Mode->Info->HorizontalResolution / 2) - (mWidth / 2);
  CoordinateY = (gGraphicsOutput->Mode->Info->VerticalResolution / 2) - (mHeight / 2);

  DisplayImageAt ((UINTN) CoordinateX, (UINTN) CoordinateY);
  
  return EFI_SUCCESS;

}

DisplayImageAt()在指定位置处,将内存mBlt中存储的图像,直接显示到屏幕上;而DisplayImage()则完成了将BMP图像加载到内存mBlt,以及调用DisplayImageAt()显示图像的功能。

3)实现弹跳函数Bounce

弹跳函数通过UEFI定时器,按一定的频率移动图像,如果图像撞到屏幕四周,则反弹回来继续移动。

逻辑结构还是比较简单的,内容如下:

EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBoundBg = {201,  174, 255, 0};
EFI_EVENT mEvent;
EFI_STATUS bounce (VOID)
{
  EFI_EVENT events[2];
  UINTN index;
  EFI_INPUT_KEY Key;
  INTN DeltaX;
  INTN DeltaY;
  INTN CurrentX;
  INTN CurrentY;
  INTN NewX;
  INTN NewY;

  
  gBS->CreateEvent (
    EVT_TIMER,
    0,
    NULL,
    NULL,
    &mEvent
    );
  gBS->SetTimer (
    mEvent,
    TimerPeriodic,
    // 10*1000*1000  //fix time? 1s
    10*1000*80   //80ms
    );

  Key.ScanCode = SCAN_NULL;
  DeltaX = 3;
  DeltaY = 3;
  events[0] = gST->ConIn->WaitForKey;
  events[1] = mEvent;
  CurrentX = (gGraphicsOutput->Mode->Info->HorizontalResolution / 2) - (mWidth / 2);
  CurrentY = (gGraphicsOutput->Mode->Info->VerticalResolution / 2) - (mHeight / 2);

  do {
    gBS->WaitForEvent (2, events, &index);

    if (index == 0)  {
      gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
      continue;

    } else {
      
      NewX = CurrentX + DeltaX;
      NewY = CurrentY + DeltaY;

      if (NewX + mWidth > gGraphicsOutput->Mode->Info->HorizontalResolution) {
        //bounce X
        NewX = gGraphicsOutput->Mode->Info->HorizontalResolution - mWidth;
        DeltaX = -DeltaX;
      } else if (NewX < 0) {
        //bounce X
        NewX = 0;
        DeltaX = -DeltaX;
      }
      if (NewY + mHeight > gGraphicsOutput->Mode->Info->VerticalResolution) {
        //bounce Y
        NewY = gGraphicsOutput->Mode->Info->VerticalResolution - mHeight;
        DeltaY = -DeltaY;
      } else if (NewY < 0) {
        //bounce Y
        NewY = 0;
        DeltaY = -DeltaY;
      }

      gGraphicsOutput->Blt(
                      gGraphicsOutput,
                      &mBoundBg,
                      EfiBltVideoFill,
                      0,
                      0,
                      CurrentX,
                      CurrentY,
                      mWidth,
                      mHeight,
                      0
                      );
      DisplayImageAt (NewX, NewY);
    
      CurrentX = NewX;
      CurrentY = NewY;
    }
   
  } while (Key.ScanCode != SCAN_END);
  gST->ConOut->Reset (gST->ConOut, FALSE);
  return EFI_SUCCESS;
}

4)实现主功能

在主程序中,添加对bmp图像的加载显示,并实现弹跳效果:

if (EFI_ERROR (DisplayImage("bounce.bmp"))) {
    return 1;
  }
  
bounce();

为了方便演示,我把屏幕分辨率改为了800×600。

经过以上步骤,就完成了弹跳小游戏的编程了。

2 测试弹跳小游戏

使用如下命令编译:

C:\vUDK2018\edk2>build -p RobinPkg\RobinPkg.dsc -m RobinPkg\Applications\BounceGame\BounceGame.inf -a IA32

把编译好的程序BounceGame.efi以及项目文件夹下的bounce.bmp拷贝到模拟器所在文件夹,运行BounceGame.efi即可看到效果。

在Tianocore模拟器中,运行效果如图2所示。

图2 弹跳小游戏

Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/BounceGame下

1,454 total views, 1 views today

发表评论

电子邮件地址不会被公开。