单机游戏架构

游戏逻辑开发进度:□□□□□□□□□□□□

本章结束开发进度:■■■■□□□□□□□□

项目代码

Github上代码有两个分支,master分支为赵童鞋实际开发时的提交分支,teach分支为根据课程内容,每章结束时提交的代码。同学们主要可以参考teach分支,每一次commit都是每章结束时的项目代码状态。

为什么会有两个分支呢?实际项目开发的时候都是往一个大方向走,总有考虑不周的情况,所以代码就会删删减减的,用来教学就会前后代码不一样,并不适合于编写教程。但是这样的代码提交却更加真实,有兴趣的童鞋可以试着阅读master分支的代码。

开发环境

教程需要PHP-7.x版本支持,PHP环境需要童鞋们自行安装配置,以下是一些有可能有帮助的资料:

Windows:www.apachefriends.org/index.html

Linux:lnmp.org/install.htm…

Mac:不存在的。。赵童鞋没玩过

引入Composer

在合适的位置新建一个文件夹HideAndSeek作为我们的项目根目录。

为了后续开发的方便,我们需要为我们的项目引入composer的自动加载机制,并在项目根目录运行以下命令:

  1. composer init

小提示:没有安装composer的童鞋需要自行安装喔:pkg.phpcomposer.com/#how-to-ins…

启用国内镜像地址,下载速度更快。

国内镜像一:composer config -g repo.packagist composer https://packagist.phpcomposer.com

国内镜像二:composer config -g repo.packagist composer https://packagist.laravel-china.org

经过几次回车之后,composer就会为我们生成一个composer.json文件,需要在这个文件中增加以下代码:

  1. "autoload": {
  2. "psr-4": {
  3. "App\\": "app"
  4. }
  5. },

再运行一次composer命令:

  1. composer install

2 单机游戏架构 - 图1

composer就会生成vendor文件夹,里面会有一个autoload.php文件,这个文件就是用来实现类自动加载机制。

建立项目结构

下一步就是编写游戏的逻辑,实现一个单机版的捉迷藏。

在上面创建的文件夹HideAndSeek中新建一个test.php文件

  1. HideAndSeek
  2. └─test.php

为什么要新建这个test.php呢?因为我们现在对整个项目的架构毫无头绪,所以需要将这个游戏逻辑拆分成几个步骤,并且将逻辑预先写在test.php文件中。

2 单机游戏架构 - 图2

赵童鞋设想的游戏逻辑是这样的:

  1. 每个玩家要有一个ID,用来区别玩家。
  2. 要有一个游戏控制器,用来创建玩家、执行移动逻辑、判断游戏是否结束。
  3. 使用游戏控制器创建玩家,满足人数后游戏开始。
  4. 使用游戏控制器控制某个玩家进行移动。

test.php文件:

  1. <?php
  2. $redId = "red_player";
  3. $blueId = "blue_player";
  4. //创建游戏控制器
  5. $game = new Game();
  6. //添加玩家
  7. $game->createPlayer($redId, 6, 1);
  8. //添加玩家
  9. $game->createPlayer($blueId, 6, 10);
  10. //移动坐标
  11. $game->playerMove($redId, 'up');

现在就很明显了,我们首要任务就是先实现这样一个游戏控制器。

HideAndSeek文件夹中创建app文件夹,app文件夹用来存放我们项目的各种类文件。并在app文件夹中创建Manager文件夹,用来存放所有管理者类的类文件,在Manager文件夹中创建Game游戏控制器类。

为了composer能够自动加载类文件,需要在Game类中加入命名空间:namespace App\Manager。细心的童鞋已经发现了,我们上面在composer.json中新增的代码里就为app文件夹注册了命名空间。

不了解自动加载机制的童鞋可以自行搜索一下composer psr-4

  1. <?php
  2. namespace App\Manager;
  3. class Game
  4. {
  5. }

我们现在要思考一下游戏还需要哪些实体类,我们捉迷藏游戏有玩家,有地图,所以游戏还需要两个实体类:一个是玩家类,另一个是地图类,没错,这就是面向对象编程,而不是面向运气编程。

app文件夹下新建Model文件夹,用来存放各种游戏实体类,并在其中新建Player类和Map类:

  1. <?php
  2. namespace App\Model;
  3. class Player
  4. {
  5. }
  6. <?php
  7. namespace App\Model;
  8. class Map
  9. {
  10. }

完善各类代码

现在我们需要思考一下,这三个类各自的属性和方法需要有哪些。

Map类

我们先从最简单的Map类开始:

做题时间

  1. Map对象应该在创建的时候可以指定长和宽,并生成一个地图。
  2. Map对象中应该有一个数组,用来保存完整的地图数据,我们假设这个数组中,0是墙,1是路。
  3. Map类中需要有一个方法来获取地图数据。

由于生成一个地图的算法有点复杂,我们现在的重点只在于编写游戏逻辑,所以地图数据可以先写死。

  1. <?php
  2. namespace App\Model;
  3. class Map
  4. {
  5. private $width;
  6. private $height;
  7. private $map = [
  8. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  9. [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
  10. [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
  11. [0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0],
  12. [0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0],
  13. [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
  14. [0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0],
  15. [0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0],
  16. [0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0],
  17. [0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0],
  18. [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
  19. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  20. ];
  21. public function __construct($width, $height)
  22. {
  23. $this->width = $width;
  24. $this->height = $height;
  25. }
  26. public function getMapData()
  27. {
  28. return $this->map;
  29. }
  30. }

Player类

第二个轮到我们的Player类:

做题时间

  1. Player对象应该有一个自己的唯一ID。
  2. Player对象需要有自己的坐标,用来标识他在地图的位置。
  3. Player对象需要有自己的类型,分为寻找者躲藏者
  4. Player类中需要有四个方向常量用来控制当前坐标,分别是 右。

    <?php namespace App\Model; class Player {

    1. const UP = 'up';
    2. const DOWN = 'down';
    3. const LEFT = 'left';
    4. const RIGHT = 'right';
    5. const PLAYER_TYPE_SEEK = 1;
    6. const PLAYER_TYPE_HIDE = 2;
    7. private $id;
    8. private $type = self::PLAYER_TYPE_SEEK;
    9. private $x;
    10. private $y;
    11. public function __construct($id, $x, $y)
    12. {
    13. $this->id = $id;
    14. $this->x = $x;
    15. $this->y = $y;
    16. }
    17. public function setType($type)
    18. {
    19. $this->type = $type;
    20. }
    21. public function getId()
    22. {
    23. return $this->id;
    24. }

    }

Game类

最后轮到我们的游戏控制器Game类啦~

做题时间

  1. 新建一个Game对象时需要生成一个地图并且保存地图数据。
  2. Game中需要一个数组保存两个玩家数据。
  3. Game中需要实现createPlayer()方法用来添加玩家。
  4. Game中需要实现playerMove()方法操作玩家移动。

    <?php namespace App\Manager;

    use App\Model\Map;

    class Game {

    1. private $gameMap = [];
    2. private $players = [];
    3. public function __construct()
    4. {
    5. $this->gameMap = new Map(12, 12);
    6. }
    7. public function createPlayer($playerId, $x, $y)
    8. {
    9. }
    10. public function playerMove($playerId, $direction)
    11. {
    12. }

    }

童鞋们先尝试实现一下createPlayer()方法和playerMove()方法应该怎么实现,我们下一章再来揭秘。

本章对应Github Commit第二章结束

当前目录结构:

  1. HideAndSeek
  2. ├── app
  3. ├── Manager
  4. └── Game.php
  5. └── Model
  6. ├── Map.php
  7. └── Player.php
  8. ├── composer.json
  9. ├── test.php
  10. └── vendor
  11. ├── autoload.php
  12. └── composer