普通视图
FC 游戏:星之卡比 梦之泉之物语(Kirby’s Adventure)
FC 游戏:洛克人六代(Rockman 6)
超级马里奥全明星
新来的99年的小孩,竟然是个古早游戏爱好者。闲聊间自然而然地提到了《超级玛丽》(SMB1)。她表示:“《超级玛丽》好玩是好玩,就是太简单了。要是设计点更难的关就好了。”
我立即表示:“有啊,日版超级玛丽2(金牌玛丽)完全符合你要求。”
随即开始心虚——这个游戏并没有真正上手,只是存在于录像里的难——万一它没那么难,我可就难看了。
那就比划比划呗。
![]()
为此我选中的是1993年老任为了纪念红白机发售10周年而在超任上推出的《超级马里奥合辑》(美版名《超级马里奥全明星》)。这部良心复刻[2]重制了红白机时代超级马里奥兄弟系列的3代共4部作品。
众所周知[3],红白机的超级玛丽2代分为日版的金牌玛丽和美版的拔萝卜,这是两个完全不同的游戏。
![]()
金牌玛丽发行在磁碟机上,此前完全没接触过;拔萝卜真机虽然没见过,但是GBA模拟器出来的时候玩过几关《超级马里奥Advance》,不到2小时便放弃。这一个半是尝鲜,是此次的重点。
所以先简单说两句伟大的SMB1和SMB3。
SMB1的影响力毋庸置疑,是红白机游戏甚至是电视游戏的代名词——自本博开博以来,18位朋友19次在留言中留下过“超级玛丽”。
因为我的小众病,若不是沾了它傻弟弟的光,再写300个红白机游戏也轮不到这款头号名作。
SMB1小时候我并不能打通关,8-4死活过不去,哪怕之前攒了十几条命,也会在见库巴前的水里变小,然后被库巴丢斧头砸死或者一腚坐死。
进入模拟器时代之后当然是第一时间报仇雪耻。在8-4开始的地方存个档,第一天没过,第二天一把就过了,真就没读第二次。从那以后我知道了,我技术也许没那么差,但是耐力和心理素质是真不太好。
![]()
SMB1的重制版本并不多。除原版和本作外,就只GBC上还有一个版本,其余的都是没有任何变化的原版移植。
SFC这版音画大大提升,赏心悦目,心旷神怡,可以说是想重温SMB1的不二之选。
![]()
不过不知道是心理作用还是年纪大了手头反应慢,感觉SFC的起跳下蹲比起红白机版粘滞,按键节奏跟30年前是不大一样的。
这次查资料还搞清了一个困扰我30多年的问题:为什么2-1、4-1、5-1等处的奖励蘑菇,跳关打就出现,而正常一关一关打就不出现呢?
原来奖励蘑出现的真正条件是前一大关的第三小关达成金币全吃。跳关的时候等同于金币全吃,奖励蘑才会必然出现。
![]()
SMB3我接触的很晚,是高二(1997年)的十一。几天假忘了,反正玩游戏的时间满打满算不到2天。因为有电软上的翻牌秘技助力,加上第三世界不费吹灰之力就能把命加满,所以第一天胡搞瞎搞就打到了第八世界。
![]()
第二天随便瞎按,发现我手上是个用SELECT就能选择所有道具的改版。但是彼时我并没有一鼓作气打通关,反而是逐一小关使用P云,妄图顶翻每一块砖头,踩遍每一个音符。沉湎在探索的乐趣中无法自拔,以至于卡还了,游戏没通关。
![]()
SMB3是红白机的动作游戏巅峰之作,充满了乐趣,建立了后续30多年里马里奥正统系列的基本模型,是多个游戏榜单上的红白机最佳游戏。但我心目中它可不是满分。因为它有个巨大的缺陷:如此丰富的内容,竟然不支持存档,也没有密码续关。一觉醒来又要从头开始的感觉太糟了。不晓得有多少人跟我一样,前三个世界从从容容游刃有余,后面几个世界匆匆忙忙连滚带爬。
![]()
直到2004年,出差期间,在3P哥的GBA上用FC模拟器才打了通关。
这次重温又过了20多年,感觉仍旧亲切且趣意盎然。“回来了,一切都回来了”,另外三个游戏加一起也不如SMB3玩的时间长。
![]()
GBA上的A4也是个不错的版本,但这一版的优点是跟合集里的一代风格统一,传承有序。
![]()
![]()
![]()
它放在合集里显得有些格格不入:别人消灭敌人都是靠踩、顶和扔子弹,而它却是拔和砸;别人高跳要靠加速跑,而它要靠长按蓄力。
![]()
拔萝卜流程很长,打法却很单调。除了一些BOSS战还能有点花样,探索可谓毫无乐趣。玩到后面都犯困了。
![]()
![]()
优点是给各色各样的敌人起了可可爱爱的名字,比如粉色恐龙比尔多,可能是这一作在系列中留下的唯一遗产。
![]()
![]()
![]()
最后是正主金牌玛丽。难确实是难,但最直观的感受还不是难,而是“恶心”,充斥着对于玩家的恶意。相对于一代新增的要素:吃掉后大变小小变无的黑蘑菇;被靠近后不会缩回的红色食人花;跳到空中超长待机不知道什么时候才会落下的绿弹簧;在空中漂浮的章鱼;从友商借鉴来的空中的风和地上的冰——没有一样是对攻关过程有正向作用的。连续出现的极限跳跃、糖葫芦串食人花红乌龟什么的是家常便饭。
![]()
最为人诟病的还是对于探索行为的嘲讽:当你兴奋地发现一条隐藏路径,以为找到了通往后续世界的捷径,谁知获得的却是巴掌呼脸:通往第一世界。然后管子旁边还人性化地给你留了个自杀用的坑,预判了你的预判。
![]()
往好了想呢,这是磁碟机游戏,开发者在善意地提醒玩家:“磁碟机能存档啊,你傻站着干什么,赶紧体验我们的读档功能啊!”
另一个开发者玩弄玩家的例子是一代中只出现过3次的迷宫,金牌玛丽里屡见不鲜。甚至后面还有一关,同样一个场景连续出现4次,让你跑得心里慌慌的,终于忍不住,尝试钻一下管子吧——然后时间就不够了。你还说你不是故意的?
在本合集中,制作者也知道自己当年的不地道,从善如流删除了把8-4通关8次才能出现后面ABCD隐藏世界的恶心设定。并且还贴心地追加了一个自杀秘技= =
![]()
因为跟一代玩法几乎没有任何区别,所以金牌玛丽在系列里唯一的亮点是路易吉有了自己不同于马里奥的个性,“脚滑的路易吉”成为独当一面的存在。
这次我打也是选了路易吉,脚滑的缺点可以用反复读档抵消,但是蹦的高带来的好处却是实打实的。挺好用。
![]()
游戏流程难得要死,BOSS跟一代差别却不大,除了多了串糖葫芦。
![]()
![]()
这部复刻合集作品最终在SFC销售榜上高居第二,我看来实至名归。哪怕是冲着音画全面加强的一代和能存档的三代,已经值回成本。另外俩纯白饶。
相关推荐:
- 丧![46%]
- 超级马里奥大陆2:6枚金币[37%]
- 瓦里奥大陆[34%]
- 超级大金刚[27%]
- 星之卡比3[26%]
FC 游戏:超级马里奥三代(Super Mario Bros. 3)
FC 游戏:忍者龙剑传三(Ninja Ryukenden III)
FC 游戏:双截龙三代(Double Dragon 3)
FC 游戏:双截龙二代(Double Dragon 2)
FC 游戏:兔宝宝大冒险(Tiny Toon Adventures)
FC 游戏:B 计划(B-Wings)
FC 游戏:热血硬派(Nekketsu Kouha Kunio-kun)
无人岛物语
秘技:开始游戏给主人公起名【いけサラ】,则游戏开始后所有角色能力值全满,并且有近乎无限的食物和水。
本作是当年闻名遐迩的小黄油,标题这几个字就令人浮想联翩,可能是同类题材的开山之作。大概在初中时期就一直惦记了,终于花时间啃了下来,成为继GBA《黄金太阳》之后又一个玩了后悔的名作。
失望之处主要在于其主线玩法的枯燥。就是开地图–采集–生产,虽然夹杂了一些GAL要素的事件,以及为某些珍贵物品设置了在不同地点找A找B找C类的RPG要素,但总体玩下来还是过于流程化了。
![]()
当然更失望的是,这个黄油它不黄啊!其实玩之前是有预感的,因为貌似SFC的正式游戏是完全没有带颜色的东西的,但这家伙又确实名声在外不是?打通关以后终于搞清了其中的关系——有颜色的是初版的PC98版,而往后虽然复刻多次,却都很正经了。
所以游戏的对话里虽然还残留了一些“有故事”的痕迹,但立绘真的从始至终十分正经。
![]()
![]()
![]()
再一个不给力的地方是并非从无到有创造逃生装置。因为隐藏剧情是“被外星人当成人类大灾变后能否绝地求生的实验品”的缘故,剧情发生的岛上有各种各样文明人生活过的遗迹,什么坏发电机、坏电动机、坏潜艇、坏变压器、坏马达之类的东西都能找到,甚至还有一台坏掉的卫星通信求救系统。这就使得更多的关键道具是来自于刮地皮捡破烂而不是凭空手搓,成就感大大降低。一共6种通关方式,完全不靠修理破烂的通关方式好像只有最低级的小木船和热气球。
![]()
材料方面难度设置的脑洞倒值得鼓励。就像如果用亚麻给4个女生做了泳装,后面逃出的时候用来做大帆船的布就不够了;橡胶只有一点点,做了飞艇就不能修卫星。这点有些资源窘迫的味道,值得表扬。
![]()
费时费力打出的隐藏结局根本不像是对玩家的鼓励。普通的好结局还回归人类社会了呢,隐藏结局竟然被放回去后被清除记忆了。普通结局还有妹子陪着,隐藏结局就只剩一只猴子了,上哪说理!
![]()
![]()
![]()
![]()
相关推荐:
- 46亿年物语[83%]
- 电精[83%]
- 大盗五右卫门2~奇天烈将军玛基斯[83%]
- 钟楼[83%]
- 重装机兵:回归[83%]
近期使用QOSAddSocketToFlow()在Windows下建立QoS踩过的坑
客户要求给原有Socket 通信增加QoS 功能,包括了Server 端和Client 端。示例代码似乎平平无奇,实装却花了三周半。尤其是最后的那个问题,困扰了我半个月。今天终于解决了,简单记录一下,希望能帮到需要的人。
第①个问题
现象:CreateQosHandle() 失败,GetLastError() = ERROR_NOT_SUPPORTED(50)
原因:CreateQosHandle()只有两个参数,出现这个错误是因为有的例子太老了,给第一个参传了{1, 1}。实际上进入Win10 时代之后第一个参就只能传{1, 0}。
解决办法:CreateQosHandle()第一个参传{1, 0}。
第②个问题
现象:QoSAddSocketToFlow() 失败,GetLastError() = WSA_INVALID_PARAMETER(87)
原因:
1)某些例子太老,第5个参传了0。新版本函数只能传两个定义好的宏:QOS_NON_ADAPTIVE_FLOW 和QOS_QUERYFLOW_FRESH,不能传0。本例的使用场景实际只能传QOS_NON_ADAPTIVE_FLOW。
2)我的PC上有两块网卡,连接内网环境的是第二块网卡。因此第2个参不能传NULL,而要通过给一个SOCKADDR结构体赋值IP 地址和Port 的方式,指定使用的网卡。
解决办法:第2个参在握手成功后过CAsyncSocket 的 GetPeerName() 取得连接用的IP地址和端口号,第5个参固定传QOS_NON_ADAPTIVE_FLOW。
第③个问题
现象:同时启动Server 和Client,Client 调用QoSAddSocketToFlow() 失败,GetLastError() = ERROR_NOT_FOUND(1168)
原因:添加QoS 的Socket 不支持用自己的Client 连接自己的Server。
解决办法:再找一台开发器。
P.S: 这个就是我上次吐槽AI的事件。
第④个问题
现象:在确定Socket握手成功的回调函数中添加QoS,Client 调用QoSAddSocketToFlow() 失败,GetLastError() = ERROR_ACCESS_DENIED(5)
原因:添加了QoS 的Server 需要管理员权限运行。
解决办法:调试时Server 端用管理员执行Visual Studio,或者给工程属性–Linker–Manifest File–UAC level 改成【requireAdministrator(/level=’requireAdministrator’)】。
第⑤个问题
现象:Client 的OnConnect(int nErrorCode) 回调中,有时nErrorCode = WSAEWOULDBLOCK(10035)
原因:这不是问题。只是Socket握手过程发生了延时。
解决办法:点两滴眼药水。
第⑥个问题
现象:Socket 连接建立后,Server 端立刻收到OnClose() 回调,并且传入的参数 = WSAECONNABORTED(10053)。
原因:既存的工程在Client创建socket的时候,立刻调用了SetSockOpt()设置了SO_LINGER,并且设定的值是{1, 0},目地是Socket Close 时不等待缓存,直接进行硬关闭。但是这个属性如果在socket握手成功前被设定,那么在调用QoSAddSocketToFlow() 的同时就会产生这样的关闭。
解决办法:将SetSockOpt 的调用时机改到socket 连接建立之后,亦即,Client 端在OnConnect(0)后调用,Server 端在OnAccept(0) 后调用。
其它说明
10035本身不用管,但是跟10053长得太像了。
Server 端也不能用127.0.0.1,不知是否跟多网卡有关。
问题③、④和问题⑥干扰的选项太多,一度非常怀疑杀毒软件、防火墙、域策略,非常混乱。
问题①、②都是通过比较不同的例子找到的破绽。
问题③靠的是CSDN上的一句吐槽。
问题④最终解决靠的是在Git上广搜例子,在一个示例的说明里看到Server侧需要在管理员权限下运行的提示,方解决。
问题⑥最后是用了排除法编程,逐行注代码的笨办法筛出来的。全网没有人遇到同样的问题。可能就没有人提前设SO_LINGER 吧……
示例代码
共通类,继承CAsyncSocket:
#pragma once
#include <afxsock.h>
#include <qossp.h>
#include <winsock2.h>
#include <qos2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "qwave.lib")
class CCommonQosSocket : public CAsyncSocket
{
public:
CCommonQosSocket()
: m_hQos(NULL)
, m_dwFlowId(0)
, m_ver({1, 0}){}
virtual ~CCommonQosSocket() {
CloseWithQos();
}
BOOL CreateQosHandle() {
if (m_hQos) {
QOSCloseHandle(m_hQos);
m_hQos = NULL;
}
if (!QOSCreateHandle(&m_ver, &m_hQos)) {
int nLastError = GetLastError();
return FALSE;
}
return TRUE;
}
BOOL GetPeerAddr(SOCKADDR_IN& peerAddr) {
int len = sizeof(peerAddr);
if (!GetPeerName((SOCKADDR*)&peerAddr, &len)) {
int n = GetLastError();
return FALSE;
}
return TRUE;
}
BOOL AddQosFlow(QOS_TRAFFIC_TYPE trafficType, SOCKADDR* pAddr) {
if (!m_hQos || !m_dwFlowId) {
return FALSE;
}
SOCKADDR* pTgtAddr(pAddr);
SOCKADDR_IN peerAddr{};
if (!pTgtAddr) {
if (!GetPeerAddr(peerAddr)) {
return FALSE;
}
pTgtAddr = static_cast<SOCKADDR*>(&peerAddr);
}
BOOL bRet = QOSAddSocketToFlow(m_hQos,
static_cast<SOCKET>(*this),
pTgtAddr,
trafficType,
QOS_NON_ADAPTIVE_FLOW,
&m_dwFlowId);
int nLastError = GetLastError();
return bRet;
}
void CloseWithQos() {
if (m_dwFlowId && m_hQos) {
QOSRemoveSocketFromFlow(m_hQos,
static_cast<SOCKET>(*this),
m_dwFlowId,
0);
}
if (m_hQos) {
QOSCloseHandle(m_hQos);
m_hQos = NULL;
}
m_dwFlowId = 0;
__super::Close();
}
private:
HANDLE m_hQos;
DWORD m_dwFlowId;
QOS_VERSION m_ver;
};
Server端部分代码:
#include "CommonQosSocket.h"
class CClientSocket: public CCommonQoSSocket {
};
class CListenSockt : public CCommonQoSSocket {
public:
virtual void OnAccept(int nErrorCode) override {
CAsyncSocket::OnAccept(nErrorCode);
CClientSocket* pNewClient = new CClientSocket;
sockaddr addr;
int iAddrLen = sizeof(addr);
if (Accept(*pNewClient, &addr, &iAddrLen)) {
pNewClient->CreateQosHandle();
if (pNewClient->AddQosFlow(QOSTrafficTypeBestEffort, &addr))
//sccess;
linger closeLinger{1,0};
(void)pNewClient->SetSockOpt(SO_LINGER, (const void*)&closeLinger, sizeof linger);
else {
//failed
}
}
};
};
void CQoSServerDlg::OnBnClickedButtonStart()
{
CClientSocket* pListen = new CClientSocket;
CString csLocalIP(L"192.168.8.4");
int nListenPort(32000);
if (!pListen->Create(nListenPort,
SOCK_STREAM, FD_READ | FD_WRITE | FD_ACCEPT | FD_CLOSE,
csLocalIP)) {
int nError = GetLastError();
AfxMessageBox(L"Listen Failed.");
return;
}
if (!m_ListenSock.Listen()) {
AfxMessageBox(L"Listen Failed.");
return;
}
}
Client端部分代码:
#include "CommonQosSocket.h"
class CClientSocket: public CCommonQoSSocket {
public:
virtual void OnConnect(int nErrorCode) overwride {
CAsyncSocket::OnConnect(nErrorCode);
if (nErrorCode) {
return;
}
CreateQosHandle();
if (this->AddQosFlow(QOSTrafficTypeBestEffort, nullptr)) {
//success
linger closeLinger{1,0};
(void)this->SetSockOpt(SO_LINGER, (const void*)&closeLinger, sizeof linger);
}
else {
//failed
}
}
};
void CQoSClientDlg::OnBnClickedButtonConnect()
{
CClientSocket* pClient = new CClientSocket;
pClient->CreateQosHandle();
CString csLocal(L"192.168.8.11");
CString csServer(L"192.168.8.4");
int nPort(32000);
pClient->Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE,
csLocal);
if (m_sock.Connect(csServer, nPort)) {
}
else {
}
}
就酱紫,找到问题⑥的原因花了13天,改掉只需要5分钟。
游戏生涯个人喜好表
上一个的姊妹问卷(https://gamegrid.shatranj.space/zh-CN)。这个就反复斟酌了好久。不作详细解释。
![]()
相关推荐:
- 最终幻想战略版Advance[22%]
- 霸王的大陆[19%]
- 与红白机的若干事[17%]
- 大航海时代2[16%]
- 战胜海马!![15%]
如何让MFC的Dialog类型窗口在高度超出屏幕高度时出现比例合适的垂直滚动条
客户提了个需求:因为他们的显示器(32吋)大,所以经常把缩放比设成125%或者150%,希望我们的APP在这两个缩放比下能够正常显示。
但是我们干活用的只是普通的24吋,设成150%之后高度就出溢出屏幕了,这就需要加滚动条。而工作这个东西,到了二鬼子领导那里就会加码,变成100%-225%都得能正常运行,并且因为增加的高度与原来的高度相比没多太多,所以要大滑块,不要分的细碎的小滑块。
这个功能本身不难。通常的做法是取屏幕放大后的窗口新高度,然后减去桌面有效视窗高度,得到的差值除以一个系数,然后用SetScrollRange的第三个数给传进去。然后重写OnVScroll方法,从系数反推滑块位置。
但是,这样得到的是小滑块,而且最后一屏的空白部分也不准确,往往会出现大片空白。
研究了好几天,终于找到了还算不错的方案。在此分享一下。
注意,我只写了垂直滚动条,因为我们的窗体就是瘦长型,即使增加到225%也没超出屏幕宽。给公家干活的一个要务就是不干多余的事,所以要添加水平滚动条的自己酌情修改,我这里就不提供了。
开始。
第一步,在OnInitDialog()中,增加垂直滚动条
如需要增加则对垂直滚动条进行初始化。初始化时,不使用简化版的SetScrollRange(),而改用SetScrollInfo()。利用结构体SCROLLINFO的nPage和nMax配合实现大滑块。这里的逻辑是:nPage与nMax的比值也就是滑块占总高度的比值,比值越接近一,滑块越大。nPage和nMax都是相对值,只要二者单位统一即可。方便起见直接使用真实值。
一个很坑的点是nMax不能用窗口Rect的高,而要取最下边控件的下沿,原因未知。
下面是代码:
BOOL CMFCAppDemoDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
//取窗口位置
CRect rcThis;
GetWindowRect(&rcThis);
//取最下面控件的位置,如果有动态创建的控件,可以遍历取得。
CRect rcLastButton;
GetDlgItem(IDCANCEL)->GetWindowRect(rcLastButton);
//取放大倍数,96.0是100%时候的DPI
float fScale = static_cast<float>(GetDpiForWindow(m_hWnd)) / 96.0;
//取桌面工作区大小
CRect rcScreen;
::SystemParametersInfo(SPI_GETWORKAREA, 0, &rcScreen, 0);
//对话框的工作区域理想高度:比最后一个控件多一丢丢。
int nHeightImage = rcLastButton.bottom + rcLastButton.Height() * fScale;
//如果想象高度比工作区域高,那么将窗口高度设为与工作区等高。
if (nHeightImage > rcScreen.Height())
{
m_blHasVScrollBar = true; //成员变量,用于标记是否有滚动条
rcThis.bottom = rcThis.top + rcScreen.Height();
this->MoveWindow(&rcThis, TRUE); //修改Dialog自身高度
SCROLLINFO si{};
si.cbSize = sizeof SCROLLINFO;
si.fMask = SIF_RANGE | SIF_PAGE | SIF_PAGE;
si.nPage = rcScreen.Height(); //Windows桌面可利用高度作为Page高
si.nMax = nHeightImage; //窗口高度最大值。
SetScrollInfo(SB_VERT, &si, TRUE); //激活滚动条
}
//否则没用滚动条
else
{
SetScrollRange(SB_VERT, 0, 0, FALSE);
}
return TRUE;
}
第二步,重写WM_VSCROLL的消息响应函数OnVScroll()
没有难点。只要每个消息处理时,nPage与nMax的比例关系一致即可。
BEGIN_MESSAGE_MAP(CMFCAppDemoDlg, CDialogEx)
ON_WM_VSCROLL()
END_MESSAGE_MAP()
void CMFCAppDemoDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CDialogEx::OnVScroll(nSBCode, nPos, pScrollBar);
//取之前的滚动条信息
SCROLLINFO si{};
GetScrollInfo(SB_VERT, &si, SIF_ALL);
//滚动条上一次的位置
int nCurPos = si.nPos;
const int FACTOR(100);
switch (nSBCode)
{
case SB_LINEUP: //Scroll one line up
nCurPos -= (si.nPage / 50); //点击一次箭头,或者按一次↑,移动页面的1/50,注意方向
break;
case SB_LINEDOWN: //Scroll one line down
nCurPos += (si.nPage / 50); //注意方向
break;
case SB_PAGEUP: //Scroll one page up
nCurPos -= (si.nPage / 50* 20); //PgUp键的处理。所有的响应要统一单位标准即可。注意方向
break;
case SB_PAGEDOWN: //Scroll one page down
nCurPos += (si.nPage / 50* 20); //注意方向
break;
case SB_THUMBPOSITION: //Scroll to the absolute position. The current position is provided in nPos
nCurPos = nPos; //从缩略图直接确认位置
break;
case SB_THUMBTRACK: //Drag scroll box to specified position. The current position is provided in nPos
nCurPos = nPos; //从滚动条直接确认位置
break;
case SB_ENDSCROLL:
break;
default:
break;
}
//确认没有超出最小值和最大值范围。最小值一般是0,最大值是nMax - nPage。
nCurPos = max(si.nMin, min(nCurPos, si.nMax - static_cast<int>(si.nPage)));
//当位置移动时,滚动窗口内容
if (nCurPos != si.nPos)
{
int nDelta = si.nPos - nCurPos; //注意方向,原始值减目标值
si.nPos = nCurPos;
si.fMask = SIF_POS;
SetScrollInfo(SB_VERT, &si, TRUE); //设滚动条
ScrollWindow(0, nDelta); //滚动窗口
UpdateWindow();
}
}
第三步,重写WM_MOUSEWHEEL的消息响应函数OnMouseWheel()
同样没有难点,只是鼠标滚动一下会转化成多次向上或向下的消息。
BEGIN_MESSAGE_MAP(CMFCAppDemoDlg, CDialogEx)
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
BOOL CMFCAppDemoDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
//确认滚动条有效
if (!m_blHasVScrollBar) {
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
CONST INT WHEEL_SCROLL_LINES(3);
UINT8 ucDirection(SB_LINEUP);
//根据zDelta方向确定消息重量
if (zDelta < 0) {
ucDirection = SB_LINEDOWN;
}
//把鼠标滚动值换算成N个箭头消息并发送。次数是没有方向的。
UINT unLines = (abs(zDelta) * WHEEL_SCROLL_LINES) / WHEEL_DELTA;
while (unLines--)
{
SendMessage(WM_VSCROLL, MAKEWPARAM(ucDirection, 0), 0);
}
return TRUE;
}
总之,最难的其实还是开头。nPage与nMax虽然设什么数都可以,但只有用真实值才是最符合拖动规律的。
地球冒险1+2
二代攻略
这次是大名作。
这两部作品是那个时代特有的日本与美版不同步的典型事例:一代本来没发行过美版。所以日版叫做Mother和Mother2,美版叫做Earthbound Beginnnings和Earthbound。
本来是只想玩2代的,毕竟自古2代出神作嘛!但是下载之后才发现,GBA上是没有单独的2代复刻的,而是复刻了1+2的合集。那就不管我赶不赶时间,一起来呗!谁知道汉化组知难而退,并没有汉化小字体满是蝌蚪文的一代!而且本作的汉化好像也是在英化版的基础上搞的,所有道具都是英文名,以至于我打游戏的时候还要去参照英文的道具列表。
![]()
复刻版分良心复刻和没良心复刻。在我看来这GBA的1代复刻版就是没良心的那种。仅仅在原版的基础上加了个跑动键,修复了几个良性BUG,就拿出来卖了。宣称声音画面都有所提升,至少我在打最终BOSS之前是没看出来。
![]()
无数欧美玩家对本作推崇备至,但在我看来只是对勇者斗恶龙的简单扒皮:国王变成女王;教会存档变成打电话存档;魔法变成超能力;怪物变成外星人;马车换成铁路,仅此而已,连队列方式和视角都跟DQ差不多,真不比FF把战斗视角改成横板更具突破性。可能也是因为《阿猫阿狗》“借鉴”得太好,珠玉在后吧。
![]()
![]()
![]()
音乐确实可圈可点,不同场景的不同背景音乐,在那个年代的RPG里算难得的了。
![]()
敌人略显变态,最后一个迷宫有能放即死魔法的,有物理攻击免疫魔法攻击只掉20滴血的,有超高攻击并且一回合打你两次的,强度拉满,弥补了最终BOSS不能打的不足。而且最后一个迷宫实在是太远了,需要无参照物绕一个大圈,累。
![]()
![]()
![]()
亏我还找了个带更强力队友打BOSS的方法,做好了打BOSS时忽然死机的心理建设。死机最终没有发生,但我带强力队友也没啥用。因为一代的最终BOSS根本就不吃任何攻击,要靠女主不停唱歌把它吓跑……
![]()
![]()
一代通关。
![]()
![]()
![]()
二代出品比一代晚了5年,平台也鸟枪换炮提升了一个等级,声音画面游戏容量提升天经地义。
![]()
但是故事方面的提升却并不多。仍旧是与外星人斗。只不过一代的外星人只会魅惑动物,二代的外星人进化到了蛊惑人心。其实还是中二少年拯救地球的大框架。遇到BOSS前仍旧是需要学会8首曲子。8首曲子前面的铺垫很长,最后三首就很紧促,仿佛干着干着钱不够了的样子。当然在电子游戏界这也是一种常态。
![]()
![]()
二代对战敌人时的背景很有设计感。
![]()
![]()
![]()
![]()
两部作品各卡关了一个地方。一代卡的地方简直可以算是一个小BUG:因为我少踩亮了一个村子,所以去打完下水道里的龙之后,没有出现通往下一个场景的路,只能钻山洞绕大远;而二代是因为因为没插耳机,所以从博物馆出来的时候,一楼的电话在嗷嗷叫唤,我愣是没发现提示。二代有个非常好的设定,就是城里会有个要饭的,在你不知道该干啥的时候给出关键提升。
![]()
![]()
二代还有一个无奈的设定,打着打着,主角会莫名其妙的想妈妈而失去战斗力。做个游戏而已,也不用这么扣题吧。
![]()
二代对战最终BOSS前增加了一个战胜自己内心的小插曲,好评。不过穿越回古代,以及灵魂出窍这样的剧情真心没什么意思。
![]()
![]()
![]()
二代的最终BOSS是个类似于地球意志的东西,并没有实体。而且搞得玄而又玄的,四个主角需要魂穿到过去的时间线,消灭萌芽中的敌人。
![]()
![]()
跟一代一样,二代的最终BOSS也还是打不死,而是要靠女主不间断地“祈祷”。很难说不是从《龙珠》那边借鉴了攒元气弹啊!
![]()
二代通关!
![]()
![]()
![]()
相关推荐:
- 星之卡比3[58%]
- 重装机兵:回归[39%]
- 星之卡比~梦之泉物语[37%]
- 卡比弹珠台[35%]
- 超级大金刚[32%]
美国角斗士
在卡带时代,这个游戏是我最后一批接触的动作类游戏之一,1996年开年的寒假,借来玩过两周。当时是盘4合1的合卡,上面写的什么名字不记得了,什么什么大对决之类的。因为没打通,所以在1999年接触模拟器后,是第一批重点复仇对象。但当时的记忆就已经产生了偏差,用“古希腊”、“对决”、“木棒”、“墙”、“运动会”之类的关键字进行搜索,结果一无所获。
后来开始这个系列,从字母A开始遍历,很快就轮到了。当时大喜过望,查了一下这个游戏的来历——American Gladiators是美国的一个竞技类娱乐节目,从1989年一直办到1996年,持续多季。有些类似国内早年引进过的西德的《夺标》,只不过德国人的游戏大多是集体项目,而这个节目尽是些个人项目。并且这个节目应该非常火爆,这款同名游戏不仅有FC版,在PC、SFC、MD上也有作品登场。油管上很容易搜到这个节目的录像。
游戏有5个小游戏组成——
JOUST,是两个人各自手持一根包了头的木棒,站在平台上互捅,把对方捅到台下算赢;
![]()
HUMAN CANNONBALL,是一方荡绳子,撞击位于平台上的另一方,把对手撞下平台算赢,否则输;
![]()
POWERBALL,场地里有5个台子和3个防守者,玩家从上下两端每次取一个球,限定时间内放满5个台子算赢,防守者可以用身体阻止玩家,有点像美式橄榄球;
![]()
ASSAULT,对手坐着能横移的叉车向外扔子弹,玩家在掩体后面拿到武器后与对方对轰,三发命中后KO对手;
![]()
WALL,就是爬墙,躲避障碍物和从各个方向出现的敌人,碰到墙或者被敌人碰到都死。
![]()
前4个小游戏都不算难,但是最后一个爬墙游戏简直是变态——给角色提供动力的方式是交替按下AB两键,有障碍有敌人,怕被敌人追上要按的快,不碰障碍还要能做到及时刹车。而且还有时间限制。而且红白机游戏有个特点——当你的右手折腾的频率越高,左手的虎口就酸得越快,小臂也会很快发僵。
我并不是个跟难度死磕的人,像忍龙那样死活过不去的游戏,也很少放在心上。但这个游戏就很会搓火,其余四个很简单,就这个爬墙,好像眼瞅着能行了,却总差那么一点儿。一不注意就搓到手抽筋也一无所获。
![]()
2005年的时候,借助即时存档功能,我曾经把5个小游戏都打过了。当时想当然的以为自己跨过了这座山。而且本作的操作性实在不好,除了掉凳时的音效以外可谓一无是处,所以A开头的游戏也没选它。
![]()
今天偶然看到一段通关视频,我才知道25岁的我还是太年轻了。我那只是通关了LEVEL1。人家真正的设置是每周目难度逐级增加,直到打通LEVEL4,才会出现极为变态的附加关,通关附加关后才会出现真正的结局。
我现在的水平,打到二周目爬墙的时候,大脑里说你还可以的,右手拇指与左手手肘已经在同时抱怨:滚犊子!
![]()
![]()
讨论红白机最难游戏的时候,本作竟然连一个提名都不能获得,这不科学!也许太多人跟我一样以为这只是个无限循环没有结局的游戏了吧。
游戏还是那么难,水平甚至比当年还退步了很多,但它再也无法在我心中掀起波澜了。所以今后我可能会陆续把这些阴影都翻出来挑战一下。
这个游戏没能通关。下面是我从别的地方搞到的通关画面:
![]()
![]()
相关推荐:
- 霸王的大陆[72%]
- 上尉密令[72%]
- 特救指令[72%]
- 摩登原始人2[72%]
- 恶魔城2:诅咒的封印[72%]