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

The Mothernode

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (4投票s)

2013 年 8 月 18 日

CPOL

10分钟阅读

viewsIcon

14601

四人科幻迷宫游戏, 包含陷阱和拾取物品。

简介 

Mothernode 是一款新的游戏,最多四名玩家互相竞争,在一个充满 随机 存储 盒子的迷宫中竞速,以到达房间中心的**安全节点**。  在他们向中心前进的过程中,玩家会获得奖励,他们可以利用这些奖励来帮助自己并阻碍他人的进程。  

该游戏设计用于 联想 Horizon 一体机 PC ,并在 2013 年“游戏”类别中获得第一轮入围者 奖项,该奖项属于英特尔® 应用创新大赛 2013 

更新 

 [2013 年 11 月 17 日] 第二轮参赛作品已提交,祝我好运。 

第二轮宣传视频  

[这是一个宣传视频,展示了游戏的各种功能]   

 

更新 

 [2013 年 10 月 18 日] 新的演示版本可供下载和测试。 

下载当前演示版 [r02]  

[此下载也可在非触摸屏 PC 上运行,但仅限单人模式]

演示视频  

[此视频展示了在简单模式下玩的三间房间]  

这是最新演示版的截图,并附有关于已做出更改的解释。 

 

添加了背景图像,使标题屏幕更加有趣。  经过在一站式设备上的测试,发现介绍屏幕还有很多空间需要填充。  添加了一个后退按钮,以及一个用于更改难度的设置菜单。  添加了教程按钮,但实际的教程功能仍在开发中。 

 

隐藏的盒子有时会根据微小的 30x16 像素图像来着色。  上面显示的图像显示了我的脸。  这没有任何作用,但我觉得它使屏幕更有趣。  无敌的红色方块现在也隐藏起来,直到它们进入玩家的视线范围内,这样可以使屏幕更具吸引力。 

 

关卡结束触发器通过使用烟火变得更加激动人心!  上面的图像显示了黄色掠夺者通过到达安全节点触发所有盒子陷阱。  当死亡浪潮到达其他所有玩家时,他们都会受到伤害。 

 

房间之间的序列已更改,使其看起来更美观。  它现在更清晰地显示了每个房间的解锁奖励,显示了该房间的获胜颜色,并播放了一个掠夺者从一个房间走到下一个房间的小动画。 

总结 

该游戏设计旨在充分利用联想 Horizon 一体机的 27 英寸炫酷显示屏、高品质音频多点触控功能。 

  

该游戏自然地针对四人一起战斗进行了优化,但该游戏也可在桌面模式下与一体机配合使用,通过鼠标进行单人游戏体验。 

 

可以在以下链接中找到一轮游戏短片。  视频展示了游戏在 Windows8 超极本上运行的情况。 

视频链接  

 

下方的图片快速概述了如何充分利用 27 英寸屏幕的每一寸空间来提供简单、竞争激烈且令人着迷的体验。  

游戏玩法 

Mothernode 游戏玩法包含一系列简单的任务,其基本内容如下所述。 

目标/目的 

每轮游戏开始时,您的掠夺者将位于屏幕的一个角落,您的目标是在任何其他掠夺者到达之前,引导您的掠夺者到达屏幕中心的**安全区**。 

阻碍您前进的是一个大型存储盒网格。 开始时您只能看到红色无敌方块以及靠近您掠夺者的两个方块。 

 

移动

游戏是实时进行的,因此您需要立即引导您的掠夺者。  要将您的掠夺者移向安全区,您的掠夺者需要打破路径上的方块。  通过触摸屏幕来定位要打破的方块。  

当方块被打破时,有些方块会授予玩家奖励。  玩家收集的所有奖励都位于掠夺者起始区域旁边。 

 

奖励 

掠夺者可以收集和使用的奖励有两种类型: 一次性奖励  衰减奖励

一次性奖励 顾名思义,就是一次性使用的奖励,例如: 

  • 医疗包 (恢复生命值),
  • 摧毁三连排 (立即摧毁一行中的三个容器)
  • 阻挡三连排 (将三个容器设置为无敌,以恰好阻止您的敌人采取捷径),
  • 设置陷阱 (使目标容器成为陷阱)  


衰减奖励 持续 30 秒。  示例衰减奖励包括:  

  • 力量增强 (增加对容器造成的伤害)
  • 陷阱感知 (将陷阱容器标红,颜色会随着使用而变淡)
  • 视野之外 (显示视线之外的容器类型,有助于规划路线——看到的距离随使用而减少) 
  • 防毒面具 (减少因毒气造成的伤害) 

 

奖励的使用方式不同。 衰减奖励通过点击奖励来激活,然后激活的奖励会出现在已激活奖励区域。  下图显示了“陷阱感知”衰减奖励已激活,并且还显示了在此奖励激活时陷阱盒的识别方式。 

 

一次性奖励 通过将它们拖动到网格中的特定方块来使用。  下图显示了“三连排摧毁”奖励从收集的奖励区域拖动到目标方块。 

 

一旦装备好,三连排奖励 就会被拖动到第二个目标方块并释放以激活。 

陷阱

除了奖励之外,一些盒子还包含陷阱。  爆炸陷阱会对掠夺者造成即时伤害,并可能摧毁相邻的盒子。  毒气陷阱是永久性陷阱,如果掠夺者站在有毒区域内,会缓慢中毒。 

 

 

回合结束反馈 

最终,一名掠夺者会到达安全区并触发回合结束。  当发生这种情况时,游戏会停止,玩家会收到关于他们表现的一些反馈。  

游戏回合 

游戏类型在下面简要描述,但它们都包含两个阶段: 

第一阶段:到达安全节点   

如上所述,玩家通过打破掠夺者通往安全节点的路径上的方块来引导他们的掠夺者。 

玩家可以在关卡中使用他们的奖励,方法是点击和/或将它们从已收集的奖励区域拖动到目标。 

多点触控对这款游戏至关重要,因为会有许多玩家同时从屏幕的四个角落进行点击和拖动。  

当一名掠夺者到达安全节点时,此阶段结束。  

第二阶段:用您的奖励换取先发优势 

房间清理完毕后,玩家有 10 秒钟的时间可以将任何未使用的奖励换取 5.0 秒的先发优势,换取 2 个奖励可获得 10.0 秒的先发优势,依此类推。  不需要使用的奖励会被弹入屏幕中心。 

此功能可以与 AIO Striker 结合使用,类似于轮盘赌中的拖动获胜筹码或饥饿的河马中的抓取球。  

当计时器用完时,此阶段结束,游戏从第一阶段重新开始。  

游戏类型 

最终版本还计划有五种游戏变体,尽管只有“最后一人站立”将在演示版中提供: 

  • 最后一人站立 - 玩家穿越一个又一个仓库,直到只有一名掠夺者幸存下来。 
  • 万众一心 - 与最后一人站立不同,玩家只有在所有人都到达安全节点才能前进。  
  • 清仓 - 最后一人站立的一个变体,但玩家必须清除设定的百分比的容器才能前进。 
  • 生命时光 - 最后一人站立的另一个变体,但玩家必须在时间用完之前到达安全节点。 
  • 哨兵 - 仓库中还包含一些简单的哨兵,如果被发现,它们会攻击掠夺者。  再次与最后一人站立类似,但增加了威胁。 

  

游戏故事  

传说,曾经强大的**银河贸易公司**(GTC)的旗舰 Champanova 在 50 年前从已知空间中消失时,载有世界一半的财富。  因此,当这艘船神秘地漂回太阳系,显然已被遗弃时,地球人口大举恐慌,试图占领这艘飞船并 claiming the treasure as their own.

你扮演一名试图到达 Champanova 控制中心**Mothernode**的众多掠夺者之一,该中心将允许访问这艘船所包含的难以想象的财富。  Mothernode 的位置未知,已在 GTC 的数据银行因金融危机而崩溃时遗失,少数知情者被折磨致死。   

掠夺者从 Champanova 的对接舱开始,必须穿过一系列连接的仓库。  通往相邻仓库的通道由存储区域中央的安全节点控制,该节点完全被存储容器包围。  安全节点允许用户访问其中一个连接的存储区域,但也会激活内部安全系统,该系统会摧毁所有剩余的存储容器。 

为了到达安全节点,掠夺者会打破他们路径上的存储容器。  有些容器比其他容器更难打破(木箱比金属箱更容易),有些则无法打破。  容器可能包含好坏惊喜。  有些容器会奖励掠夺者(额外的速度、额外的生命值、现金等),有些则会造成问题(爆炸、释放有毒气体等),掠夺者需要逃离,或使用防护措施。  

该游戏面向青少年和年轻成年人。  虽然没有血腥场面,也没有冒犯性的武器,但游戏的基调和风格针对的是更成熟的观众。   

开发  

该游戏正在使用 The Game Creators (TGC) 的 App Game Kit (AGK) v108 进行开发。  我选择这个环境是因为我以前使用 AGK 开发过许多游戏,我认为这种语言非常易于使用,并且提供了访问 AIO 功能的出色函数。 

下面的示例函数展示了如何处理触摸交互。 

function DetermineTouchInput()
//determines what action each touch/release is related to
    // actions are:
    // * set target
    // * select/drag bonus
    // * arm a container
    // * trigger bonus
    beingtouched = getrawfirsttouchevent(1)
    while beingtouched>0
        if DebugON>0
            px# = screentoworldx(getrawtouchlastx(beingtouched))
            py# = screentoworldy(getrawtouchlasty(beingtouched))
            setspritepositionbyoffset(TouchCrossSpt,px#,py#)
            setspritevisible(TouchCrossSpt,1)
        else
            setspritevisible(TouchCrossSpt,0)
        endif
        if getrawtouchvalue(beingtouched)>0
            //dragging bonus
            //arming container
            //using bonus
            if getrawtouchreleased(beingtouched)=1
                //dropping bonus
                //find bonus and player
                usprite = getrawtouchvalue(beingtouched)
                for p=0 to 3
                    for b=1 to TotalBonusTypes
                        if PlayerBonus[p,b].count > 0
                            if PlayerBonus[p,b].UsingSprite = usprite
                                pid = p
                                bid = b
                                p = 3
                                b = TotalBonusTypes
                            endif
                        endif
                    next b
                next p
                //arming or using
                if bonus[bid].NeedsArm = 1
                    //check for container
                    px# = screentoworldx(getrawtouchlastx(beingtouched))
                    py# = screentoworldy(getrawtouchlasty(beingtouched))
                    //-32.0
                    xtile = trunc((px#+32)/64)
                    ytile = trunc((py#+32)/64)
                    ArmBonus(pid, bid, Xtile, Ytile)
                else
                    //trigger bonus
                    TriggerBonus(pid, bid, 0, 0, 0, 0)
                endif
                //clear dragged sprite
                usprite = getrawtouchvalue(beingtouched)
                if getspriteexists(usprite)=1 then deletesprite(usprite)
                setrawtouchvalue(beingtouched,0)
            else
                //just dragging
                usprite = getrawtouchvalue(beingtouched)
                px# = screentoworldx(getrawtouchcurrentx(beingtouched))
                py# = screentoworldy(getrawtouchcurrenty(beingtouched))
                //-32.0
                setspritepositionbyoffset(usprite,px#,py#)
            endif
        else
            //selecting target container
            //selecting bonus
            //draging armed container
            if getrawtouchreleased(beingtouched)=1
                //activating?
                //targetting?
                px# = screentoworldx(getrawtouchstartx(beingtouched))
                py# = screentoworldy(getrawtouchstarty(beingtouched))
                //-32.0
                //translate to tile coordinates
                xtile = trunc((px#+32)/64)
                ytile = trunc((py#+32)/64)
                if StorageArea[xtile,ytile].armbonusid>0
                    //tile is armed
                    xend# = screentoworldx(getrawtouchlastx(beingtouched))
                    yend# = screentoworldy(getrawtouchlasty(beingtouched))
                    //-32.0
                    xetile = trunc((xend#+32)/64)
                    yetile = trunc((yend#+32)/64)
                    dext = trunc(xetile - xtile)
                    deyt = trunc(yetile - ytile)
                    if StorageArea[xtile,ytile].armbonusid=1 or StorageArea[xtile,ytile].armbonusid=2
                        //3 in a row
                        if abs(dext)>2.0 or abs(deyt)>2.0
                            p = StorageArea[xtile,ytile].armplayerid
                            b = StorageArea[xtile,ytile].armbonusid
                            if abs(dext)>abs(deyt)
                                if dext>0
                                    dext = xtile+2
                                else
                                    dext = xtile
                                    xtile = xtile-2
                                endif
                                deyt = ytile
                            else
                                dext = xtile
                                if deyt>0
                                    deyt = ytile+2
                                else
                                    deyt = ytile
                                    ytile = ytile-2
                                endif
                            endif
                            TriggerBonus(p, b, xtile, ytile, dext, deyt)
                        endif
                    endif
                    if StorageArea[xtile,ytile].armbonusid=5 or StorageArea[xtile,ytile].armbonusid=12
                        //2 x 2
                        if abs(dext)>1.0 and abs(deyt)>1.0
                            p = StorageArea[xtile,ytile].armplayerid
                            b = StorageArea[xtile,ytile].armbonusid
                            if dext>0.0
                                dext = xtile+1
                            else
                                dext = xtile
                                xtile = xtile-1
                            endif

                            if deyt>0.0
                                deyt = ytile+1
                            else
                                deyt = ytile
                                ytile = ytile-1
                            endif
                            TriggerBonus(p, b, xtile, ytile, dext, deyt)
                        endif
                    endif
                    if StorageArea[xtile,ytile].armbonusid=6 or StorageArea[xtile,ytile].armbonusid=11
                        //single square
                        p = StorageArea[xtile,ytile].armplayerid
                        b = StorageArea[xtile,ytile].armbonusid
                        TriggerBonus(p, b, xtile, ytile, xtile, ytile)
                    endif
                    StorageArea[xtile,ytile].armbonusid = 0
                else
                    //not armed so target?
                    tsprite = StorageArea[xtile,ytile].ShroudSpriteID
                    class = StorageArea[xtile,ytile].class
                    if getspriteexists(tsprite)=0 and class>0 and xtile>1 and xtile<areamaxx and ytile>1 and ytile<areamaxy
                        //target
                        for p=0 to 3
                            if player[p].health>0
                                psx# = getspritexbyoffset(Player[p].spriteid)
                                psy# = getspriteybyoffset(Player[p].spriteid)
                                tdist# = sqrt(((psx#-px#)^2)+((psy#-py#)^2))
                                if dist#=0.0
                                    dist# = tdist#
                                    selectedplayer = p
                                else
                                    if tdist#<dist#
                                        dist# = tdist#
                                        selectedplayer = p
                                    endif
                                endif
                            else
                                spriteid = Player[p].TargetSpriteID
                                setspritevisible(spriteid,0)
                            endif
                        next p
                        //set target box
                        spriteid = Player[selectedplayer].TargetSpriteID
                        setspritevisible(spriteid,1)
                        setspritepositionbyoffset(spriteid,xtile*64,ytile*64)
                        Player[selectedplayer].TargetX = getspritexbyoffset(spriteid)
                        Player[selectedplayer].TargetY = getspriteybyoffset(spriteid)
                    endif
                endif
            else
                //selecting bonus?
                px# = screentoworldx(getrawtouchstartx(beingtouched))
                py# = screentoworldy(getrawtouchstarty(beingtouched))
                //-32.0
                //translate to tile coordinates
                xtile = trunc((px#+32)/64)
                ytile = trunc((py#+32)/64)
                if StorageArea[xtile,ytile].armbonusid>0
                    //armed
                    lx# = (64.0*xtile)-32.0
                    ly# = (64.0*ytile)-32.0
                    ex# = screentoworldx(getrawtouchcurrentx(beingtouched))-32.0
                    ey# = screentoworldy(getrawtouchcurrenty(beingtouched))-32.0
                    DrawLine( lx#, ly#, ex#, ey#, 255, 0, 0 )
                else
                    //select bonus
                    //if py#<96 or py#>((areamaxy-1)*64)
                        for p=0 to 3
                            for b=1 to TotalBonusTypes
                                if PlayerBonus[p,b].count > 0
                                    bspriteid = PlayerBonus[p,b].spriteid
                                    if getspritehittest(bspriteid,px#,py#)=1
                                        //clone sprite
                                        usprite = clonesprite(bspriteid)
                                        setspritedepth(usprite,1)
                                        //PlayerBonus[p,b].UsingOffsetX = px# - getspritexbyoffset(bspriteid)
                                        //PlayerBonus[p,b].UsingOffsetY = py# - getspriteybyoffset(bspriteid)
                                        PlayerBonus[p,b].UsingSprite = usprite
                                        setrawtouchvalue(beingtouched,usprite)
                                        p = 3
                                        b = TotalBonusTypes
                                    endif
                                endif
                            next b
                        next p
                    //endif
                endif
            endif
        endif
        beingtouched = getrawnexttouchevent()
    endwhile
endfunction  

上面的代码虽然有点复杂,但它捕获了游戏中的所有触摸命令。  这些命令包括: 

  • 设置掠夺者的目标,
  • 激活衰减奖励,
  • 将一次性奖励拖动到方块以进行武装, 
  • 将一次性奖励从武装方块拖动,
使这些命令更复杂的事实是,任何四名玩家都可能同时进行。  幸运的是,AGK 语言通过类似以下命令使其成为可能: 

beingtouched = getrawfirsttouchevent(1) 

该环境会自动检测触摸事件并将其存储在列表中。  触摸事件还被分类为“短”、“长”、“拖动”和“未知”类型。  命令getrawfirsttouchevent(1) 从列表中提取第一个触摸事件。  参数“1”允许返回所有触摸事件,包括“未知”类型。 

一旦返回触摸事件 ID,就可以提取以下参数: 

  • 起始坐标,
  • 当前坐标,
  • 最后一个坐标(保留给已释放的触摸事件)
  • 释放状态,
  • 触摸时长。 

BASIC 语言的这些功能使我能够快速开发游戏。 

控制每个回合的整个循环如下所示: 

repeat
    FrameTime = (getframetime()*1000)
    SecondCounter = SecondCounter-FrameTime

    CheckTraps()
    DetermineTouchInput()

    if HumanPlayers<4
        for p=0 to 3
            if player[p].Human < 1 and player[p].health>0
                MoveAI(p)
            endif
        next p
    endif

    MovePlayers()
    AnimatePlayers()
    MoveNewBonuses()
    ManageActiveBonuses()

    if SecondCounter<=0
        for p=0 to 3
            PlayerLevelStats[p].TimeTaken = PlayerLevelStats[p].TimeTaken +1
        next p
        SecondCounter = SecondCounter+1000
    endif

    if getrawkeyreleased(13)=1 then FakeExit = 1

    sync()

until InNodeZone>3000 or FakeExit = 1

AGK IDE 将源代码编译成字节码,然后由优化的“播放器”运行。  该播放器利用 OpenGL,并且是专门开发的,以便在处理大量精灵时保持高帧率。  

该游戏于 8 月初开始开发,并且已经达到了可玩的成熟alpha状态,正如视频 链接所示,可以 在此处下载。  为确保游戏与 AIO PC 的兼容性,该游戏正在一台 Windows8 超极本上开发和测试。  使用超极本主要是因为该设备也具有触摸屏界面,并且与 AIO 具有相同的操作系统。  尽管超极本的屏幕尺寸和整体性能不如 AIO,但这些限制确保了 AIO 上可能实现的高性能,并且在第二台机器上进行开发可以确保开发环境不会影响游戏的安装。 

兴趣点 

联想一体机 PC 非常适合运行 Mothernode 之类的多人桌面游戏。  Mothernode 游戏充分利用了 AIO 的大型 27 英寸屏幕、10 点多点触控功能及其Striker 配件,为玩家带来前所未有的实时多人桌面游戏体验。 

© . All rights reserved.