65.9K
CodeProject 正在变化。 阅读更多。
Home

虚拟 Monopoly

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2013年9月2日

CPOL

7分钟阅读

viewsIcon

15805

对最受欢迎的家庭游戏之一《大富翁》的改造。

引言

虚拟大富翁使用 AIO 平台,最多可供 6 名玩家玩。将 AIO 像原始棋盘游戏一样平放在桌子上,玩家将能够通过触摸传感器与游戏互动。其理念是尽可能保留棋盘游戏中亲身体验的部分,允许玩家拖动自己的棋子移动允许的格数,滑动手指翻牌,拖动新购买的房产到指定位置,并与虚拟银行系统互动进行交易。27 英寸 AIO 平台的屏幕空间将提供舒适愉悦的视角,同时为每个玩家提供游戏板周围(屏幕边缘)的指定区域,称为“用户角”,以在游戏过程中访问统计信息。玩家还可以选择通过智能手机管理他们的用户角。高清图形将显示令人惊叹的视觉效果。这将是迄今为止最令人愉悦和互动的大富翁类棋盘游戏。

背景   

虚拟大富翁是经典游戏《大富翁》的彻底重制。游戏的图形主题将采用太空时代的形式。房产将是空间站和基地,游戏棋子将是与太空相关的物体,如航天飞机,街道将被行星、星座和星系取代。几乎所有人都熟悉大富翁,如果不熟悉,维基百科提供了所有历史信息以及原始游戏的玩法。游戏玩法说明将以 PDF 格式随虚拟大富翁一起提供。

这款游戏将把大富翁的乐趣带入新一代,同时具有一定的教育意义。在玩游戏时,玩家将通过可识别、逼真且吸引人的图形了解宇宙。预计将看到的星系有银河系、仙女座、半人马座和欧米茄星系等。铁路将由四颗恒星取代,分别是天狼星、波江座ε、大角星和太阳。监狱将是一个超大质量黑洞。

使用代码 

该游戏将使用 Visual C++ 创建。通过使用 .NET 语言,它将为与朋友在线游戏铺平更轻松的道路。标准 Windows 8 API 包含了所有我需要实现此功能的触摸功能。

游戏的视觉/图形将在我使用更简单的位图图形(作为占位符)使代码稳定运行后创建。

一个重要的功能将是掷骰子功能。可以选择使用 AIO 提供的电子骰子,或者使用虚拟骰子,后者将在指定区域滑动两根手指来掷出。我将为每个骰子使用两个 1-6 之间的随机数生成器,并返回每个值,以便显示正确的骰子面并允许玩家移动相应的格数。

int rolls = 0;
while(rolls <= 1) {
    faceValue = 1 + rand() % 6;
    if(rolls == 0) {
        dice.setValue1(faceValue);
    }
    else if(rolls == 1) {
        dice.setValue2(faceValue);
    }
    else {
        return;
    }
    rolls++;
}

如您所见,创建了类来保存信息。将有骰子、棋盘和玩家的类,以及其他游戏组件,如随机卡片。`Player` 类将保存位置、金钱、房产、卡片等值。在游戏中使用图形时,这些类将非常重要,因为需要检索信息以显示其正确的图形。

ref class Bank {
    private:
        double _500s;
        double _100s;
        double _50s;
        double _20s;
        double _10s;
        double _5s;
        double _1s;
    public:
        Bank() {
            Clear();
        }
    void set(double a, double b, double c, double d, double e, double f, double g) {
        _500s = a;
        _100s = b;
        _50s = c;
        _20s = d;
        _10s = e;
        _5s = f;
        _1s = g;
    }
    double get500s() {
        return _500s;
    }
    double get100s() {
        return _100s;
    }
    double get50s() {
        return _50s;
    }
    double get20s() {
        return _20s;
    }
    double get10s() {
        return _10s;
    }
    double get5s() {
        return _5s;
    }
    double get1s() {
        return _1s;
    }
    double getTotal() {
        return (_500s * 500) + 
            (_100s * 100) + 
            (_50s * 50) + 
            (_20s * 20) + 
            (_10s * 10) + 
            (_5s * 5) + _1s;
    }
    void Clear() {
        _500s = -1;
        _100s = -1;
        _50s = -1;
        _20s = -1;
        _10s = -1;
        _5s = -1;
        _1s = -1;
    }
};

游戏板动画示例如下:

void pictureBox1_Paint(Object ^obj, System::Windows::Forms::PaintEventArgs ^e) { 
    Graphics ^g = e->Graphics;
    g->PixelOffsetMode = PixelOffsetMode::HighSpeed;
    float by_x, by_y;
    by_x = gameBoard.getByX();
    by_y = gameBoard.getByY();
    Image ^BoardImg = Image::FromFile(L"graphics\\gameboard.png");
    g->DrawImageUnscaled(BoardImg,(int)(gameBoard.getByX()),(int)(gameBoard.getByY());
    int count = Players.getCount();
    if(count > 0) {
        for(int i = 0; i<count; i++) {
            int toy = Players.getToy(i);
            switch(toy) {
            case 1:
                Image ^pImg = Image::FromFile(L"img\\shuttle.png");
                break;
            case 2:
                Image ^pImg = Image::FromFile(L"img\\probe.png");
                break;
            case 3:
                // etc... insert all pieces
                break;
            }
            g->DrawImage(pImg,(int)((Players.getPosX(i)-1000)/by_x),
            (int)((Players.getPosY(i)-1000)/by_y),(int)(2000.0f/by_x),(int)(2000.0f/by_y));
        }
   } 

接下来将设计 16 张机会卡和 16 张公共财富卡的算法。在两个类中,将存储所有可能的卡片值。在每场游戏开始时,卡片会加载到两个数组中并洗牌,因此没有两场游戏会有相同的卡片序列。使用随机数生成器,值 1-16 将分配给每个牌组中的每张卡片。这将是每个牌堆的数字顺序。棘手的部分是确保在洗牌过程中,同一牌组中没有两张卡片被分配相同的数字值。为此,我们使用 Fisher-Yates 洗牌算法来确保这种情况不会发生。算法示例代码:

int arrayOrder[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16 };
Random rand = new Random();
for (int i = arrayOrder.Length - 1; i >= 0; i--) {
    int r = rand.Next(0, i+1);
    int temp = arrayOrder[i];
            arrayOrder[i] = array[r];
            arrayOrder[r] = temp;
}
for(int i = 0; i <= deck1->Count; i++) {
    static_cast<Decks^>(deck1[i])->setOrder(arrayOrder[i]); 
} 

在编码时,重要的是要牢记未来的实现。这是为自己铺平道路并留下开放选择的最佳时机。通过将所有内容编码到类中,在游戏稳定发布第一个版本后,通过 JSON 序列化将此游戏更新为多人网络游戏将更容易实现。

Objects 类将用于创建每一张牌。使用 ArrayList,我可以创建两副独立的牌,每副 16 张。Decks 类存储牌的类型、在牌堆中的位置以及将显示的消息等信息。牌可以被读取、放回或从牌堆中删除。会保留剩余牌的数量。从 ArrayList 中移除一张牌很可能意味着这张牌被移动到其中一个玩家的拥有中(例如“出狱卡”),在这种情况下,另一个 Players 类将保存该信息以及该玩家的所有房产/金钱。以下是 Decks 类在代码中如何处理的示例:

ref class Decks {
    private:
        int card;
        int order;
        String ^msg;
    public:
        Decks() {
            Clear();
        }
        Decks(int _card, int _order, String ^_msg) {
            set(_card,_order,_msg);
        }
        void set(int _card, int _order, String ^_msg) {
            card = _card;
            order = _order;
            msg = _msg;
        }
        int getCard() {
            if(this == nullptr)
                return -1;
            return card;
        }
        int getOrder() {
            if(this == nullptr)
                return -1;
            return order;
        }
        String ^getMsg() {
            if(this == nullptr)
                return L"";
            return msg;
        }
        void setOrder(int x) {
            order = x;
        }
        void Clear() {
            if(this == nullptr)
                return;
            card = -1;
            order = -1;
            msg = gcnew String(L"");
        }
};

ref class Objects {
    private:
        ArrayList ^deck1;
        ArrayList ^deck2;
    public:
        Objects() {
            Clear();
        }
        void addToDeck1(int card, int order, String ^msg) {
            deck1->Add(gcnew Decks(card,order,msg));
        }
        void addToDeck2(int card, int order, String ^msg) {
            deck2->Add(gcnew Decks(card,order,msg));
        }
        void removeFromDeck1(String ^msg) {
            for(int i = 0; i<deck1->Count; i++) {
                if(msg->Equals(static_cast<String^>(static_cast<Decks^>(deck1[i])->getMsg()))) {
                    deck1->RemoveAt(i);
                    break;
                }
            }
        }
        void removeFromDeck2(String ^msg) {
            for(int i = 0; i<deck2->Count; i++) {
                if(msg->Equals(static_cast<String^>(static_cast<Decks^>(deck2[i])->getMsg()))) {
                    deck2->RemoveAt(i);
                    break;
                }
            }
        }
        int getDeck1Count() {
            return deck1->Count;
        }
        int getDeck2Count() {
            return deck2->Count;
        }
        Decks ^getCardFromDeck1(int index) {
            if(index+1 > deck1->Count)
                return nullptr;
            return static_cast<Decks^>(deck1[index]);
        }
        Decks ^getCardFromDeck2(int index) {
            if(index+1 > deck2->Count)
                return nullptr;
            return static_cast<Decks^>(deck2[index]);
        }
        void Clear() {
            deck1 = gcnew ArrayList();
            deck2 = gcnew ArrayList();
        }
}; 

Windows 触摸输入事件处理程序用于将您所需的效果链接到点击或滑动操作。玩家在掷骰子后可以选择在游戏板上滑动他们的棋子。Windows 8 内置了许多基本功能,因此鼠标点击事件等应该适用于在 Windows 8 中运行的桌面应用程序,但要执行精确功能,您需要将事件指向触摸输入。在以下示例中,我将演示我使用“平移”手势在游戏板上移动游戏棋子的方法(请记住,使用不同的输入方法有不止一种方法可以做到这一点)。

GESTUREINFO  gi;
bool isHandled = false;
Point ^first = gcnew Point();
Point ^second = gcnew Point();

Players ^CurrentHero = Players.getActive();

switch(gi.dwID) {
    case GID_PAN:
        switch(gi.dwFlags) {
            case GF_BEGIN:
                first.X = gi.ptsLocation.x;
                first.Y = gi.ptsLocation.y;
                CurrentHero.highlight(first.X, first.Y);
                Players.CurrentHero = PointToClient(gcnew Point(first.X, first.Y) );
                isHandled = true;
                break;
            case GF_END:
                PlayLoop(false);
                isHandled = true; 
            case default:
                second.X = gi.ptsLocation.x;
                second.Y = gi.ptsLocation.y;
                CurrentHero.move(second.X,second.Y);
                Players.CurrentHero = PointToClient(gcnew Point(second.X, second.Y) );
                isHandled = true;
                Invalidate();
                first = second;
                break;
        }
    case default:
        isHandled = false;
}

用户角应用程序将使用 OpenFL 框架开发,以支持 iOS 和 Android 设备。英特尔的通用连接框架中间件使智能手机能够与 Windows 操作系统通信。如果智能手机和计算机在同一网络中,则可以实现 Wifi 连接。只需配对设备即可实现蓝牙连接。

兴趣点

  • 针对 2013 年英特尔应用创新大赛一体机平台游戏类别
  • 使用线程绘制和运行游戏逻辑
  • 使用对象类,序列化类将更容易实现多人网络
  • 使用 Windows 8 API 集成触摸功能和鼠标点击事件以实现向后兼容性
  • AIO 平台的大小非常适合作为游戏板。过去,在 PC 上与多名玩家玩经典棋盘游戏令人沮丧,仅仅是因为 PC 的尺寸(更不用说它都显示在立式显示器上)。将 AIO 平放允许多名用户舒适地围坐在游戏板周围。
  • 新的电子骰子技术增强了玩经典棋盘游戏的用户体验
  • 多用户多点触控技术允许指定的银行家进行银行交易,即使另一位玩家正在进行他/她的回合。每个用户在屏幕的任一边缘都有一个指定区域,用于查看他们的银行、房产和其他此类统计信息,这被称为“用户角”(银行家在他们的用户角中拥有银行特权)。
  • 用户角将利用英特尔的 CCF 中间件实现多设备功能,使其可以通过智能手机访问。与智能手机的连接可以通过 Wifi 或蓝牙实现。
  • 在线多人模式将成为可能,因此并非所有玩家都必须亲自在场(用户角仅向本地机器上的用户显示)。

历史

  • 2013年9月1日:这是文章和所有已创建代码的初稿。
  • 2013年9月2日:添加了类代码的延续,以处理两个牌组和用于洗牌的算法。
  • 2013年9月3日:添加了触摸输入如何链接到游戏棋子移动的示例,更多关于游戏主题的背景信息,以及用户体验的预期。
  • 2013年9月4日:添加了关于与智能手机一起使用的用户角应用程序的信息。
© . All rights reserved.