找回密码
 立即注册
查看: 910|回复: 0

[简易教程] 一个 2D 横版像素游戏的制作流程是怎样的?

[复制链接]
发表于 2021-3-17 19:13 | 显示全部楼层 |阅读模式
题主是一个像素游戏爱好者,有意制作一部2d横版像素游戏,准备登陆iOS,大概就像像星露谷物语,莫莫多拉,洞穴探险,泰拉瑞亚,这样的横版2d像素游戏,一般是用的什么游戏引擎制作?又用的什么绘图工具和动画制作工具和用什么编程语言好?请大神们多多指导,感谢。
发表于 2021-3-17 19:16 | 显示全部楼层
作为一个像素风格的爱好者。
分享一下自己的经验吧。
一、像素游戏使用的引擎



先介绍题主感兴趣的这些作品使用的引擎吧。
StardewVally 星露谷物语
由呵呵鱼发行。
开发者是ConcernedAPE
根据开发者的答疑,游戏使用的是C# XNA。


开发者本人在Reddit上的答疑链接
ConcernedAPE的轻博客链接


Terraria也同样是使用C# XNA
程序是Andrew Spinks
可以参考GIT上泰拉瑞亚代码 csnxs/Terraria
StarBound我就不多赘述了。


OwlBoy
这款游戏也是题主想要做的横版2D类型。
同样使用C# XNA开发
下面介绍的这些作品都是使用GameMakerStudio开发的


HyperLightDrifter
使用GameMakerStudio开发
这款游戏在steam 以及 PlayStation上均有上架


开发者的tumblr链接


undertale
IGN满分作品
作者是 Toby Fox
使用GameMakerStudio开发
个人觉得不好玩 T_T




downwell
作者是 Moppin
使用GameMakerStudio开发
http://downwellgame.com/


另外推荐观看《Branching Paths》这个纪录片。
相对于《独立游戏大电影》,《Branching Paths的知名度没有那么高,但是对于从业者来说绝对值得一看。
影片链接
NintendoSwitch以及任天堂其他主机上的一些像素游戏,我不是很清楚,现在懒得去查。
Octopath Traveler
开发商SquareEnix
使用的是虚幻4引擎 (开发团队是过去的勇气默示录开发团队)
这款游戏应该会在NS上首发,体验版很惊艳。
下面着重介绍一下国产的像素游戏。
如果题主有心的话,可以直接联系上开发者。


CatchMe
作者是 @玫瑰狗 @绯色de弦月
使用的是UNITY引擎
Steam链接




元气骑士
作者是 凉屋游戏 @一张钞票
使用的是Unity引擎
TapTap链接






锻冶屋英雄谭
作者是 @拼命玩三郎
引擎未知(希望可以告知补充 T_T)
TapTap链接




武器店物语
作者是 @timchen
使用的是UNITY引擎
Steam链接


To The Moon
作者是 高瞰(加拿大华人)
使用的是rpgmaker
TapTap链接


当然还有很多国人开发的像素游戏,如有忽略还请见谅。


引擎介绍用了很多篇幅,主要是因为这一部分确实很重要。
建议题主根据自己的能力选择合适的引擎。
个人推荐使用UNITY引擎,其次推荐GameMakerStudio。
我自己在用的就是UNITY引擎。




我在开发中的小游戏
二、像素游戏开发经验



绘图工具的话,我使用的是PhotoShop
其他工具的话推荐Aseprite


介绍一些PS像素资源处理的基础知识吧。
开始所有工作之前,首选项里把图像插值改为“邻近”,标尺改为“像素”,文字改为“点”。


在放大像素资源的时候记得使用“邻近”


效果示意
不使用邻近插值放大会导致资源“糊"掉
PS资源导出
动作的每一帧都制作完毕后,使用PS自带的脚本就可以快速导出了。
点击【文件】->【脚本】->【将图层导出到文件】
记得修改资源的文件名格式,勾选上透明区域。(根据自己需求)
这样就可以把动作的每一帧都导出了。


不过这样导出的文件名字里会有 “_xxxx”的无用字符,如果手动一个一个删去就太麻烦了。
这里给大家提供一个PYTHON脚本。(这里要感谢我们组的程序同学)
import sys
import os
import re

png_file = re.compile(".*\d{4}_.*\.png")
png_file2 = re.compile(".*\d{4}[a-zA-Z]_.*\.png")
png_file3 = re.compile("\d{4}_")
png_file4 = re.compile("\d{4}[a-zA-Z]_")

files = os.listdir(".")

for filename in files:
    if png_file.match(filename):
        newFilename = re.sub(png_file3, "", filename)
        if png_file2.match(newFilename):
            newFilename = re.sub(png_file4, "", newFilename)
        os.rename(filename, newFilename)
        print "rename", filename, "->", newFilename使用这个脚本就可以把文件名中的 "_xxxx"去除了。


字体相关
上上图中的字体是使用宋体12点
其他像素字体的话推荐
不过一般宋体就够用了,实在不行可以自己点。




像素画教程






推荐看这位大神的轻博客(需要科学上网)


UNITY使用相关
Unity资源导入后 记得勾选 point
Point对应 邻近插值
Bilinear对应 两次立方


左侧资源FilterMode为Point
右侧资源FilterMode为Bilinear
音效相关
一般来说开发像素游戏的话,一般都会配上8bit音效。
在这里就加上一些8bit音效的小知识吧。
在开发初期一般都需要临时的音效资源。
这部分音效资源可以在魔王魂找到完全免费的音效资源
魔王魂网站链接
当然如果你想自己制作音效,那当然更好。
FamiTracker 专门制作NES 8BIT音效,这个软件完全免费。
FamiTracker
SFX MIXER 专业制作音效
SFX MIXER网页链接
YMCK是日本一个8BIT音乐创作团队,他们有免费提供 Magical 8bit Plug 插件
YMCK链接


其他的还有
Bfxr. Make sound effects for your games.
Best Free Bitcrusher VST/AU Plugins!
https://8bitmixtape.github.io/


有一些比较细或者深入的知识我没有放在文章里面。
如果还有别的问题也可以留言回复或者私信我,我尽量解答。


有想从事像素游戏开发的美术、音效、程序、策划同学欢迎私信联系我!!
有想从事像素游戏开发的美术、音效、程序、策划同学欢迎私信联系我!!
有想从事像素游戏开发的美术、音效、程序、策划同学欢迎私信联系我!!


例行附图


补充一些我喜欢的像素艺术吧。








希望能有更多的人感受到像素风格的魅力
最后,欢迎关注我的专栏。
十八的游戏开发笔记

十八:游戏数值到底怎么设计?(一) · 数值设计的依据

十八:游戏策划眼中的阴阳师

十八:游戏是什么?这个问题真的很重要吗?

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-3-17 19:20 | 显示全部楼层
呃..我是一个游戏专业的大四学生...做了三年像素游戏...全都是像素游戏....只做过像素游戏...而且手里正在做的毕设也是像素游戏...
我也很无奈啊...我是一个程序....跟我长期合作的美术...虽然很屌...但只爱像素...情有独钟...如痴如醉...但是 像素 真的 很棒!!!很有魅力!!!
游戏引擎:Unity(没用过别的 cocos您还是别考虑了 其他的引擎教学资源也少)
里面需要注意的问题:
1)像素图片的导入,FilterMode要选择Point,不然图片在场景里会糊。

2)像素风其实分为两种

我的美术坚持认为retro style才是真·像素。推荐你看一篇文章..我这里没翻墙软件,你可以关注一下这个博主,搜一下这篇文章。这个博主日常更新一切与像素相关的艺术。

unity里面怎么实现这种效果,参见一下我之前提的一个问题。
unity 2d如何才能做到真·像素旋转?图片边缘的锯齿大小怎么才能调成和素材图片的像素格一样大?
下面有几个答主的答案很有参考价值。
就是Pixel Perfect的问题... asset store里面推荐两个插件...




3)个人觉得像素特效最难做...一般都是美术画序列帧,如果想做粒子特效的画,审美很重要。推荐steam里面的Dead ceils 那个游戏的像素特效就很棒。推荐一些像素特效插件:


4)画像素的工具,跟我合作的美术一般都是用PS画的..就是一个个像素格为单位画的。一个独立游戏制作人师哥向我们推荐了下面这个软件,风评很不错。不是很建议直接把非像素的美术素材直接在相机上加个什么像素滤镜来达到所谓的像素风格...我认为这是作弊...

MagicaVoxel 推荐一个体素工具...简单易用好上手...
5)大面积的像素可以用这个:Dan Fessler | Blog 适合画背景。
6)角色的可以参考这个:http://makegames.tumblr.com/post/42648699708/pixel-art-tutorial
7)说一下关于像素的一些教程(请合理上网)
HD Index Painting in Photoshop (by Dan Fessler)
在PS中绘制高清索引图
No Bullshit Pixel Art Tutorial (by Retronator)
绝不扯淡像素画教程
Pixel Art Knowledge (by various Pixel Artists)
像素画教程大合集
Pixel Art Tutorial (by Derek Yu)
Pixel Art Tutorial (by Cure)
Pixel Art Tutorial (by Android Arts)
Pixel Art Tutorials (by drbubu)
Pixel Art Tutorial (by Studio Purloux)
Pixel Art Academy (by Retronator)
Pixel Purism: Process vs Results (by Dan Fessler)
Pixelation Knowledge Repository (by Helm)
Pixel Art Tutorials (by finalbossblues)
像素艺术知识库
6 Pixel Art Drawing Techniques as Video (by GDQuest)
像素画六大技法视频版
What the Heck is Pixel Art (by Pix3M)
像素画到底是什么
Pixel Art Process - or 8 steps to a polished result (by Cyangmou)
像素画处理(8步美化像素画)
Perfect Pixel Art with Specular and Bump Lighting in Unity (by Anton Kudin)
在 Unity 中运用镜面光与凹凸贴图创建完美像素画
ISOmetric Pixel Art Cheat Sheet (by Dennis Busch)
等轴立体像素画技法小抄
How To Pixel Art Tutorial as Video (by Christopher Yabsley)
如何绘制像素画视频版
Pyxel Edit Tutorial Mini Series as Video (by Christopher Yabsley)
像素画编辑小教程视频版
GIMP Pixel Art Tool Setup Tutorial as Video (by Retronator)
GIMP 像素工具设置教程视频版
Run Cycle Tutorial (by Glauber Kotaki)
像素画多帧循环教程
A Little Guide to Pixel Art (by Simon Hunter)
像素画迷你指南
Secret Weapons and Tactics for Better Pixel Art (by 2D Will Never Die)
精进像素艺术的神器秘籍
8)steam上好多独立的像素游戏,enter the gungeon无论是玩法还是游戏美术上都挺不错的。steam上搜pixel一大堆,自己多参考别人的东西。至于程序部分,unity 2d教程网上有的是,像素游戏毕竟也是2d的,学习方法都差不多。引擎给封装好了很多东西...题主问用啥编程语言..其实不是很重要的...
9.5)Unity中提升像素字体清晰度 - 黑桃花 - 博客园 这是最近遇到的一个问题及解决方法,当然最好是用bitmap这种工具做自己的像素字体...
9)一个程序小白的unity 2D游戏开发的反思与经验 这是大三做的一款像素游戏的经验总结...现在看来没什么参考意义... 嗯...也是个横版过关游戏...符合题主的要求...里面有宣传小片的连接...以及..找个靠谱点的策划...什么游戏的开发流程都是类似的...
10)像素游戏真的很棒!如果可以,想一直做下去!

附上正在做的一个多人的像素游戏...
有问题问吧..虽然我很渣...尽量解答...

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-3-17 19:22 | 显示全部楼层
提几个关键字吧,属于用的比较多的
tilemap : 一个开源的2D地图编辑器
引擎:cocos2d-x 或者 unity,这两个引擎都有插件支持tilemap
动画工具:直接使用引擎内置的动画编辑器,根据需要看是否使用2D avata
像素点阵画的话,网上有很多工具,一搜一大把,实际使用起来应该区别不大
发表于 2021-3-17 19:25 | 显示全部楼层
补充一个影响品质感的问题:Pixel Perfect
简单来说,我们在游戏中控制物体移动并不是按照像素为单位的,而是以浮点数为单位。
这会造成像素“抖动”,如果旋转或缩放,还会造成崩坏的情况。
如果引擎使用Unity的话,这里有插件帮助解决这个问题。
Asset Store
这个问题说起来比较抽象,自己看一看视频比较容易明白。Youtube搜关键词“unity pixel perfect”
发表于 2021-3-17 19:26 | 显示全部楼层
对于这个场景你是不是很熟悉?
对于找游戏时你是不是这种感觉?
对于玩游戏你是不是有这种感觉:
氪金时你是不是这种感觉:


没错,这不是毒鸡汤,这是一个赤裸裸的现实:
你能怎么办,退坑保平安还是无能狂怒.还是当个老老实实的吃瓜群众?
归根结底,别人的游戏终归会让你憋屈,玩得多了,也会有人说你有网瘾需要电电才老实.所以说少整些那么多有的没的.
那么有没有一款游戏,玩起来不憋屈,还能学到东西,别人不仅不会说你玩物丧志,还会说你牛逼?当然有!那款游戏叫自己做的游戏.
你想想,还有啥游戏,比自己做游戏好玩,规则自己定,不怕猪队友,为所欲为有木有
啥,需要团队,美工,音效,程序猿.资金链?不需要的,下面就由本老司机教各位如何用2小时(划掉) 两天 撸出一款游戏然后爽两个月.
那么小葵花课堂开课啦,不会码不会画只会玩怎么办,多半是废了,电一电就好了(划掉)
没关系,我们的口号是,小学生灵魂画师的水平,中等程度的码农技能点,你就能玩一个叫"我自己做的游戏"的游戏了
准备工具 硬件部分:ipad 和pencil,也可以用数位屏,数位板,电脑:
准备工具 软件部分(Visual Studio 2010,PainterEngine):
Visual Studio是宇宙最强IDE(不接受反驳),笔者用的是Visual Studio 2010版本,简单来讲就是编程开发用的,不过用啥玩意编程并不绝对重要,你可以换用其它版本的Visual Studio或者Visual Studio code,Qt,DEV C++,codeblocks,C Free都没问题。
而关于PainterEngine说明一下,PainterEngine 是一个高度可移植完整开源的图像渲染器,或者说PainterEngine是一个游戏引擎,你可以在这个回答中找到我之前写的PainterEngine的教程
https://www.zhihu.com/question/35391145/answer/718766924
很多人认为PainterEngine就是个简单的UI绘图库,实际上并不是这样。等会将会演示PainterEngine在游戏的快速开发中是如何运用的,而这也是PainterEngine这个引擎设计的最终目的。
在这里下载PainterEngine:
matrixcascade/PainterEngine别忘了点个Star:
目标:两天撸一款游戏
游戏类型:3A大作没钱,贪吃蛇,连连看太low,2小时的时间也不多,那做个什么游戏好呢,显然你要问我,答案就只有灰机大战了.
开发语言:C语言,说到这里,估计评论区又有一堆杠精跳出来说,哎呀,什么年代了还用C语言做游戏,为什么不用Unity啊,为什么不用那么多知名游戏引擎啊,多高大上还简单,C语言不适合做游戏,效率太低,要用C#,JS,引擎要用C++,面向对象支持泛型balabala…,对于这些笔者混迹码农界十五六余年什么样的杠精没见过,也懒得多费什么口水讲道理,简单总结一点就是这么干我乐意,你喜欢你自己用,反正我不用.
当然,本文技术部分面向有一定编程基础及有一定美术基础的看官们,如果对这两方面不是很熟悉的看官可以权当做看个乐子,把成品下载下来爽爽就OK,动手能力强的看官可以对照本篇文章琢磨琢磨,这都欢迎。
起名

既然是做游戏,2小时的时间恐怕也不用写什么策划文案了,第一步当然是给游戏起个响亮的名字,幸好起名字不用啥时间,而且整个游戏项目就自己一个人,也省了讨论的麻烦,既然是灰机大战,那突出主题,明确重点,既要高大上,又要和时代接轨,最后带点中国特色 索性就叫space old driver好了(当然我知道这是chinese english,这点就别杠了)
首先,创建PainterEngine项目,关于创建项目这部分,你可以在
https://www.zhihu.com/question/35391145/answer/718766924
找到详细的教程,这里我再重新复述一遍
安装PainterEngine

PainterEngine无需额外的安装步骤,你只需要从GitHub或其它渠道下载PainterEngine的代码就可以了。
1. 访问网址:https://github.com/matrixcascade/PainterEngine
2. 点击Clone or download---->Download Zip.
3. 解压下载的压缩包
4. 打开解压压缩包,再解压压缩包中的PainterEngine Tools
完成后你会得到这样的文件夹
使用PainterEngine

以Visual Studio 2010为例:
第一步:打开Visual Studio


第二步,点击新建项目
第三步,点击visual C++,选择一个空项目,然后随便填一个名字后点击确定
*第四步,右键点击项目,选择添加,新建筛选器,然后把筛选器取名为PainterEngine,这步只是建议,可以选择不做.
第五步,将PainterEngine复制到这个项目的目录下,右键点击项目.点击在windows资源管理器中打开文件夹.
第七步,把PainterEngine这个目录拖动到Visual Studio项目中(如果有做第四部就把他拖到筛选器中)
第八步,右键源代码-->添加--->新建项


第九步:创建main.c(或者别的什么名字.c)你可以开始写代码了
创建运行时环境代码

因为PainterEngine的内存管理由独立于操作系统的内存池接管,在创建游戏代码之前,先对游戏运行时资源进行一个初步的规划
游戏运行时总共的内存:64M
当中包括了:
  用于UI的内存:2M
用于加载资源内存:32M
  用于游戏逻辑运算的内存:16M
800x600的主要渲染表面:2M
临时计算缓存:8M
剩余的4M内存,预留用于,内存池内部的节点分配,碎片消耗等.
同时,游戏需要具备控制台,音效播放,字模加载等额外功能,这也就意味着游戏需要用到混音算法和字模库
为此,笔者创建了一个SOD_Runtime.h和SOD_Runtime.c用于管理这些库
/*SOD_Runtime.h*/

#ifndef SOD_RUNTIME_H
#define SOD_RUNTIME_H

#define SOD_RUNTIME_WINDOW_WIDTH 800 //游戏窗体宽度
#define SOD_RUNTIME_WINDOW_HEIGHT 600//游戏窗体高度

#include "../../PainterEngine/Architecture/PainterEngine_Console.h"

typedef struct
{
        PX_Runtime runtime;//PainterEngine运行时环境
        PX_SoundPlay sound;//混音器
        PX_Console Console;//控制台
        PX_FontModule fontmodule;//字模库
}SOD_Runtime;

px_bool SOD_RuntimeInitialize(SOD_Runtime *runtime);//游戏运行时初始化代码

#endif

/*SOD_Runtime.c*/

px_byte SOD_RUNTIME_MEMORY[1024*1024*64];//游戏运行时内存空间

#define SOD_RUNTIME_UI_SIZE 1024*1024*2 //用于UI的内存
#define SOD_RUNTIME_RESOURCES_SIZE 1024*1024*48//用于资源的内存
#define SOD_RUNTIME_GAME_SIZE 1024*1024*12//用于游戏逻辑运算的内存

px_bool SOD_RuntimeInitialize(SOD_Runtime *sodruntime)
{
        PX_IO_Data data;
        //初始化游戏运行时代码
        if (!PX_RuntimeInitialize(&sodruntime->runtime,SOD_RUNTIME_WINDOW_WIDTH,SOD_RUNTIME_WINDOW_HEIGHT,SOD_RUNTIME_MEMORY,sizeof(SOD_RUNTIME_MEMORY),SOD_RUNTIME_UI_SIZE,SOD_RUNTIME_RESOURCES_SIZE,SOD_RUNTIME_GAME_SIZE))
        {
                return PX_FALSE;
        }
        //初始化控制台
        if (!PX_ConsoleInitialize(&sodruntime->runtime,&sodruntime->Console))
        {
                return PX_FALSE;
        }
  //初始化混音器
        if(!PX_SoundInit(&sodruntime->runtime.mp_game,&sodruntime->sound))
        {
                return PX_FALSE;
        }
        //读取字模文件
        data=PX_LoadFileToIOData(SOD_FONTMODULE_PATH);
       
        if (data.size==0)
        {
                return PX_FALSE;
        }
        //加载字模文件
        if (!PX_FontModuleLoad(&sodruntime->fontmodule,data.buffer,data.size))
        {
                return PX_FALSE;
        }
        return;

}创建游戏调度器代码

游戏调度器主要的目的是游戏在不同的功能间切换,在本游戏中,用到的界面主要有三个,一个是进入游戏时的启动界面,一个是游戏界面,另一个是加载replay的界面(就是简单的一个加载文件对话框)
为此,笔者创建了一个SOD_Game.h和SOD_Game.c来统一管理游戏的内存和逻辑界面调度关系:
先来看看文件SOD_Game.h
#ifndef SOD_GAME_H
#define SOD_GAME_H

#include "SOD_Runtime.h"


typedef struct
{
        //这部分的代码待完善
        SOD_Runtime sodrt;
}SOD_Game;//调度器结构体

px_bool SOD_GameInitialize(SOD_Game *game);//游戏初始化,其中游戏运行时的代码也由调度器负责调用初始化
px_void SOD_GamePostEvent(SOD_Game *game,PX_Object_Event e);//事件处理,用于接收IO设备比如键盘鼠标触摸屏游戏杆的事件,将事件分发到合适的界面中
px_void SOD_GameUpdate(SOD_Game *game,px_dword elpased);//游戏循环更新

px_void SOD_GameRender(SOD_Game *game,px_dword elpased);//游戏渲染
#endif然后是SOD_Game.c
#include "SOD_Game.h"

px_bool SOD_GameInitialize(SOD_Game *game)
{
       
        if (!SOD_RuntimeInitialize(&game->sodrt))//初始化游戏运行时
        {
                return PX_FALSE;
        }

        return PX_TRUE;
}

px_void SOD_GamePostEvent(SOD_Game *game,PX_Object_Event e)
{
        PX_ConsolePostEvent(&game->sodrt.Console,e);//将IO事件派发给控制台
}

px_void SOD_GameUpdate(SOD_Game *game,px_dword elpased)
{
        PX_ConsoleUpdate(&game->sodrt.Console,elpased);//更新游戏控制台
}

px_void SOD_GameRender(SOD_Game *game,px_dword elpased)
{
        PX_SurfaceClear(&game->sodrt.runtime.RenderSurface,0,0,game->sodrt.runtime.RenderSurface.width-1,game->sodrt.runtime.RenderSurface.height-1,PX_COLOR(255,255,255,255));//清理渲染表面
        PX_ConsoleRender(&game->sodrt.Console,elpased);//渲染控制台界面
}完成上述代码后,游戏的最基本的初始化工作就算完成了具体的实现请看SOD_Runtime.c文件,因为windows的主函数文件main.c是之前就写好的模板,虽然代码较多但改动其实一两分钟就完成了,这里就不贴出来了,main.c主要内容是读取键盘设备的信息,将混音器的PCM数据写到系统缓存中播放,最后将PainterEngine渲染后的图形缓存显示出来,这里就不贴出来的,游戏代码完全开源,有兴趣可以打开看看.
运行界面试试:
启动界面

启动界面就是指游戏程序打开后用户第一个看到的界面,首先鉴于时间关系,就随便涂鸦一个背景就好:
启动后一共只需要三个按钮,开始游戏,回放,退出游戏,为了支持中文字模,使用PainterEngine tools里的字模生成工具,字体使用不需要商业授权的幼圆.ttf,编码常用asc码英文字符和几个用得到的汉字
内容如下
~!@#$%^&*()_+`1234567890-
=qwertyuiop[]\asdfghjkl;'zxcvbnm,./QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?开始游戏退出回放分数将上述文本以txt文件保存,编码为UFT16-LittleEndia
生成sod.pfx字模文件,然后使用PainterEngine加载该字模最后绘制在界面上
其中,SOD_StartUp.h及SOD_Startup.c代码用于渲染及执行启动界面的逻辑.你可以在源码中查看它详细的实现细节.
运行截图:
游戏界面

点击开始游戏后,进入的就是游戏界面了,为此,笔者新建了2个文件分别是SOD_Play.h SOD_Play.c,游戏的运行代码就在这两个文件内完成
创建世界和飞船

使用PX_WorldInit创建一个世界,这个函数在SOD_PlayReset(SOD_Play *pPlay)函数中被调用,在每次点击开始游戏时,都会调用SOD_PlayReset函数,创建世界后,默认会开启辅助线绘制,在“星际老司机”这款游戏中,默认创建一个长宽为1600*1200的世界
同时,用一个结构体来描述飞船的基本属性
typedef struct
{
px_bool show;//是否显示飞船

px_int life;//飞船的声明
px_texture *shipObject;//纹理对象

px_point velocity;//加速度
px_point force;//推力
px_point direction;//方向

px_int max_life;//最大生命
px_float max_force;//最大受力
px_float max_speed;//最大速度
px_bool showHelpLine;//辅助线
}SOD_Play_Object_Ship; 在px_void PX_Object_ShipUpdate(PX_Object *pObject,px_uint elpased)函数中,通过对飞船的参数物理描述,对飞船的物理信息进行更新,
在px_void PX_Object_ShipRender(px_surface *psurface, PX_Object *pObject,px_uint elpased)函数中,对飞船在世界中进行绘制,详细的代码细节你可以在SOD_Play.c中找到
运行界面如图
创建飞船的动力系统及约束

现在需要解决的一个问题是,如何操控飞船,这直接决定了这款游戏的玩法,为此,笔者设计了一种飞船的操作方式,并将它叫做弹簧操作系统
飞船的推动力方向永远跟随着鼠标方向,并且距离鼠标的距离越远,拉力力度越大.
同时,要注意飞船的飞行范围在地图内,需要不断检查飞船的位置避免它飞出地图外了.
运行截图如下:
进一步完善UI

到这里,飞船的操作基本完成了,现在将飞船的一些基本信息显示出来,一来玩家需要看到一些实际需要的参数让游戏进行下去,二来也能让游戏显得更加高大上.
在px_void SOD_PlayRenderUI(SOD_Play *pPlay)函数中你可以看到UI的具体实现细节
主要包括了几个方面
1. 绘制鼠标的标识
2. 绘制推动力指示环
3. 绘制生命条
4. 当前分数
5. 当前游戏时间
6. 武器栏
运行截图:


喷射粒子

尽管飞船有了基本的动力系统和操作,但是现在仅仅只是一个移动的贴图,缺少视觉上和操控上的感觉,因此,使用粒子系统来实现尾部火焰的喷射效果,
在SOD_Play.h中:
PX_Object *ship_powerpartical_main,*ship_powerpartical_l1,*ship_powerpartical_l2;
用于描述飞船的两个主推动器和两个副推动器的粒子对象,这些对象在SOD_PlayReset()函数中完成初始化,并在SOD_Play.c的SOD_Object_ShipUpdate函数中完成位置与角度的更新.
运行截图如下:


完善武器系统

动力系统完成后,接下来就开始完成武器系统了,因为只是一个小小的DEMO,所以没有搞得太复杂就只做了一种武器(如果你想多加点武器系统可以通过自己修改源代码来加),笔者琢磨了一番,那种打击感强烈,玩起来最舒服的想来想去还是Machine Gun,因此一个machine gun武器系统诞生了,不过,为了避免让游戏变得太没有难度.笔者还是给武器系统加上了一些限制,比如对子弹数量加了一些限制:
第一个敌人:陨石

武器系统就绪后,是时候加上一些最基础的敌人了,作为一个宇宙空间老司机,自然少不了在小行星带里的一堆陨石中玩漂移,为了让陨石的生成更加的真实点,让陨石的大小在一个范围内随机缩放,同时花了两分钟画了3张陨石的纹理贴图,然后让陨石能够有一个角速度进行自转.你可以在SOD_Play.c中找到SOD_Object_Stone前缀的函数,那里描述了陨石的执行逻辑关系.


第二个敌人 幽灵:

加入陨石后,第二种敌人也如法炮制(无非就把贴图改一下),不过为了让游戏更有意思一点,我们让第二种敌人当玩家在它附近的时候,他会毫不犹豫的黏上去,当然,第二个敌人使用了PainterEngine中的2dx动画集,让它看起来更有趣一点,你可以在SOD_Play.c中关于SOD_Object_Ghost或带有Ghost的函数中看到这个幽灵的实现细节:
第三个敌人:外星人

这个就厉害了,星系的外星人是暴躁老哥一族的,当你进入它的射程范围内时,它会毫不犹豫向你发射电浆炮,同时如果你的炮弹击中了它,它会马上启动超级暴怒模式,开着飞船就向着你撞过来
升级系统

即使是作为一个开发时间不到两天的DEMO小游戏,升级系统仍然是一个不能漏掉的东西.毕竟打怪不能升级多没意思,作为一个老司机,通过击破陨石幽灵或者外星人有一定的几率会掉落升级星星,拾取后将会对飞船性能有一点提升或者恢复生命补充弹药:
加点音效

最后,给游戏加点音效,但作为一个音痴,不会做音效怎么办.好说,你可以选中加个群,然后去找群里的大佬要点音效,做一个结结实实的白嫖怪
或者你可以用”万能问题解决器” “买!买!买!”
最后,笔者从epic stock media 购买了一堆音效,并将它们应用到了游戏当中
当然,PainterEngine使用的是自带的混音算法,你可以在PX_Sound.h PX_Sound.c中查看他们,关于其他的版权说明,你可以在resources文件夹下的copyrights找到
完成游戏

最后,是时候给这个小小的DEMO游戏收个工了,设计游戏的Game Over界面,上面要显示最终的得分,好让玩了高分的玩家可以截图出来嘚瑟嘚瑟,Replay暂时先不弄了,改成About吧
最后Release游戏(很重要,PainterEngine debug发布有很多调试算法和内存检测算法会拖累游戏性能导致FPS下降,Release发布后就没有这个问题了)
最后,录制游戏先爽爽:
==========================================================
华丽的分割线
==========================================================
如何玩游戏

一: 当然是尽量打高分咯,比如挑战10000分,可惜的是,即使作为作者我也最高只能玩到5000分左右
二: 魔改游戏,想法我都想好了,星际老司机大战滑稽Boss怎么样.
首先找一张滑稽的贴图


然后Photoshop处理一下,将它转换为traw格式加入到游戏资源当中
来到星际老司机的源代码,复制alien和alienfireatom的代码,分别描述的是外星人和它发射的子弹,修改一下
//////////////////////////////////////////////////////////////////////////
//User fire
//子弹碰撞直接删除
px_void SOD_OnUserBulletDamage(PX_Object *pObject,PX_Object_Event e,px_void *ptr)
{
        SOD_Play *pPlay=(SOD_Play *)pObject->User_ptr;
        PX_WorldRemoveObject(&pPlay->world,pObject);//删除子弹
        SOD_CreateExplosion(pPlay,PX_POINT(pObject->x,pObject->y,0),1);//创建爆炸效果
}

//子弹碰撞
px_void SOD_OnUserBulletImpact(PX_Object *pObj,PX_Object_Event e,px_void *ptr)
{
        PX_Object_Event pe;
        SOD_Play *pPlay=(SOD_Play *)pObj->User_ptr;
        PX_WorldRemoveObject(&pPlay->world,pObj);//删除子弹
        SOD_CreateExplosion(pPlay,PX_POINT(pObj->x,pObj->y,0),10);//创建爆炸效果
        pe.Event=SOD_OBJECT_EVENT_DAMAGE;//damage消息
        pe.Param_int[0]=SOD_USER_BULLET_DAMAGE;//伤害量
        PX_ObjectPostEvent((PX_Object *)e.param_ptr[0],pe);//投递消息
}

SOD_Play_Object_UserBullet *SOD_Object_GetUserBullet(PX_Object *pObject)
{
        return (SOD_Play_Object_UserBullet *)pObject->pObject;
}

px_void SOD_Object_UserBulletFree(PX_Object *pObject)
{
       
}

px_void SOD_Object_UserBulletUpdate(PX_Object *pObject,px_dword elpased)
{
        SOD_Play *pPlay=(SOD_Play *)pObject->User_ptr;
        SOD_Play_Object_UserBullet *pbl=SOD_Object_GetUserBullet(pObject);

        pbl->rotation+=pbl->rotationSpeed*elpased/1000;//更新旋转角度

        pObject->x+=pbl->velocity.x*elpased/1000;//依据速度更新位置
        pObject->y+=pbl->velocity.y*elpased/1000;

        //子弹越界(超出地图范围),删除
        if (pObject->x<-SOD_STONE_GEN_SPACE-1||pObject->x>pPlay->world.world_width+SOD_STONE_GEN_SPACE+1||\
                pObject->y<-SOD_STONE_GEN_SPACE-1||pObject->y>pPlay->world.world_height+SOD_STONE_GEN_SPACE+1\
                )
        {
                PX_WorldRemoveObject(&pPlay->world,pObject);

        }
        else
        {
                //生存时间过,删除
                if (pbl->alive>elpased)
                {
                        pbl->alive-=elpased;
                }
                else
                {
                        //删除子弹
                        PX_WorldRemoveObject(&pPlay->world,pObject);

                        //创建爆炸效果
                        SOD_CreateExplosion(pPlay,PX_POINT(pObject->x,pObject->y,0),1);
                }
        }
}

px_void SOD_Object_UserBulletRender(px_surface *psurface,PX_Object *pObject,px_dword elpased)
{
        SOD_Play_Object_UserBullet *pbl=SOD_Object_GetUserBullet(pObject);
        //渲染子弹
        PX_TextureRenderEx(psurface,pbl->tex,(px_int)pObject->x,(px_int)pObject->y,PX_TEXTURERENDER_REFPOINT_CENTER,PX_NULL,1.0f,pbl->rotation);
}


PX_Object* SOD_Object_UserBulletCreate(SOD_Play *play,px_float x,px_float y,px_float velocity)
{
        PX_Object *pObject;
        SOD_Play_Object_UserBullet *pbl;
        px_point shippt,atom_v;

        pbl=(SOD_Play_Object_UserBullet *)MP_Malloc(&play->runtime->runtime.mp_game,sizeof(SOD_Play_Object_UserBullet));

        if (pbl==PX_NULL)
        {
                PX_ASSERT();
                return PX_NULL;
        }

        pObject=(PX_Object *)PX_ObjectCreate(&play->runtime->runtime.mp_game,PX_NULL,0,0,0,0,0,0);

        if (!pObject)
        {
                MP_Free(&play->runtime->runtime.mp_game,pbl);
                PX_ASSERT();
                return PX_NULL;
        }

        pObject->x=x;
        pObject->y=y;
        pObject->z=SOD_ATOM_Z;
        pObject->Width=48;
        pObject->diameter=48;
        pObject->Height=48;
        pObject->pObject=pbl;
        pObject->Type=SOD_OBJECT_TYPE_USER_ATOM;
        pObject->ReceiveEvents=PX_TRUE;
        pObject->Func_ObjectFree=SOD_Object_UserBulletFree;//回调
        pObject->Func_ObjectUpdate=SOD_Object_UserBulletUpdate;
        pObject->Func_ObjectRender=SOD_Object_UserBulletRender;
        pObject->User_ptr=play;
        pObject->impact_Object_type=SOD_IMPACTTEST_OBJECTTYPE_ENEMY;//碰撞类型
        pObject->impact_test_type=SOD_IMPACTTEST_OBJECTTYPE_USER;//碰撞测试类型

        shippt.x=play->shipObject->x;
        shippt.y=play->shipObject->y;
        shippt.z=0;
        atom_v=PX_PointSub(shippt,PX_POINT(x,y,0));
        pbl->tex=PX_ResourceLibraryGetTexture(&play->runtime->runtime.ResourceLibrary,SOD_KEY_TEX_HUAJI);//绑定纹理
        pbl->velocity=PX_PointMul(PX_PointUnit(atom_v),velocity);
        pbl->alive=SOD_USER_ATOM_ALIVE_TIME;//生存时间
        pbl->rotationSpeed=360;//初始角速度

        PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_IMPACT,SOD_OnUserBulletImpact,PX_NULL);//处理碰撞事件
        PX_ObjectRegisterEvent(pObject,SOD_OBJECT_EVENT_DAMAGE,SOD_OnUserBulletDamage,PX_NULL);//处理伤害事件
        return pObject;
}

//////////////////////////////////////////////////////////////////////////
//user Object

SOD_Play_Object_UserObject *SOD_Object_GetUserObject(PX_Object *pObject)
{
        return (SOD_Play_Object_UserObject *)pObject->pObject;
}

//处理用户对象伤害事件
px_void SOD_Object_UserObject_OnDamage(PX_Object *pObject,PX_Object_Event e,px_void *ptr)
{
        SOD_Play_Object_UserObject *UserObject=SOD_Object_GetUserObject(pObject);
        SOD_Play *pPlay=(SOD_Play *)pObject->User_ptr;
        if (UserObject->life<=e.Param_int[0])//血扣完了
        {
                UserObject->life=0;
                //删除对象
                PX_WorldRemoveObject(&pPlay->world,pObject);
                //创建爆炸效果
                SOD_CreateExplosion(pPlay,PX_POINT(pObject->x,pObject->y,0),192);
                pPlay->score+=100;
        }
        else
        {
                //扣除血量
                UserObject->life-=e.Param_int[0];
                UserObject->beAttackElpased=0;
        }
}


px_void SOD_Object_UserObjectFree(PX_Object *pObject)
{
        SOD_Play_Object_UserObject *puserObj=SOD_Object_GetUserObject(pObject);
        //释放动画集
        PX_AnimationFree(&puserObj->user_animation);
}

//对象更新
px_void SOD_Object_UserObjectUpdate(PX_Object *pObject,px_dword elpased)
{
        SOD_Play *pPlay=(SOD_Play *)pObject->User_ptr;
        SOD_Play_Object_UserObject *puo=SOD_Object_GetUserObject(pObject);
        px_point shippt,user_v;

        puo->atom_elpased+=elpased;
        //假如飞船还存活
        if (pPlay->shipObject->Visible)
        {
                if (SOD_ALIEN_SEARCH_DISTANCE>PX_PointDistance(PX_POINT(pObject->x,pObject->y,0),PX_POINT(pPlay->shipObject->x,pPlay->shipObject->y,0)))
                {
                        //向飞船发射子弹
                        if (puo->atom_elpased>1000)
                        {
                                PX_Object *pfa;
                                puo->atom_elpased=0;
                                //发射子弹
                                pfa=SOD_Object_UserBulletCreate(pPlay,pObject->x,pObject->y,SOD_USER_ATOM_SPEED);
                                if (pfa)
                                {
                                        PX_WorldAddObject(&pPlay->world,pfa);
                                }
                                //播放发射子弹音效
                                SOD_PlaySoundFromResource(pPlay->runtime,SOD_KEY_SND_ALIEN_FIRE,PX_FALSE);
                        }
                }
                //修正速度,追踪ship
                shippt.x=pPlay->shipObject->x;
                shippt.y=pPlay->shipObject->y;
                shippt.z=0;
                user_v=PX_PointSub(shippt,PX_POINT(pObject->x,pObject->y,0));
                user_v=PX_PointMul(PX_PointUnit(user_v),SOD_USEROBJECT_SPEED);
                puo->velocity=user_v;
        }

        puo->beAttackElpased+=elpased;
        //依据速度更新当前位置
        pObject->x+=puo->velocity.x*elpased/1000;
        pObject->y+=puo->velocity.y*elpased/1000;

        if (pObject->x<-SOD_STONE_GEN_SPACE-1||pObject->x>pPlay->world.world_width+SOD_STONE_GEN_SPACE+1||\
                pObject->y<-SOD_STONE_GEN_SPACE-1||pObject->y>pPlay->world.world_height+SOD_STONE_GEN_SPACE+1\
                )
        {
                //删除对象
                PX_WorldRemoveObject(&pPlay->world,pObject);
        }
        //更新动画信息
        PX_AnimationUpdate(&puo->user_animation,elpased);
}

//渲染对象
px_void SOD_Object_UserObjectRender(px_surface *psurface,PX_Object *pObject,px_dword elpased)
{
        SOD_Play_Object_UserObject *pou=SOD_Object_GetUserObject(pObject);
        if (pou->beAttackElpased<50)
        {
                PX_TEXTURERENDER_BLEND blend;
                blend.alpha=1;
                blend.hdr_B=0;
                blend.hdr_G=0;
                blend.hdr_R=1;
                PX_AnimationRender(psurface,&pou->user_animation,PX_POINT(pObject->x,pObject->y,0),PX_TEXTURERENDER_REFPOINT_CENTER,&blend);
        }
        else
                PX_AnimationRender(psurface,&pou->user_animation,PX_POINT(pObject->x,pObject->y,0),PX_TEXTURERENDER_REFPOINT_CENTER,PX_NULL);

}

PX_Object* SOD_Object_UserObjectCreate(SOD_Play *play,px_int life)
{
        PX_Object *pObject;
        SOD_Play_Object_UserObject *pou;

        pou=(SOD_Play_Object_UserObject *)MP_Malloc(&play->runtime->runtime.mp_game,sizeof(SOD_Play_Object_UserObject));

        if (pou==PX_NULL)
        {
                PX_ASSERT();
                return PX_NULL;
        }

        pObject=(PX_Object *)PX_ObjectCreate(&play->runtime->runtime.mp_game,PX_NULL,0,0,0,0,0,0);

        if (!pObject)
        {
                MP_Free(&play->runtime->runtime.mp_game,pou);
                PX_ASSERT();
                return PX_NULL;
        }

        pObject->x=play->world.world_width/2.0f;
        pObject->y=play->world.world_height/2.0f;
        pObject->z=SOD_ALIEN_Z;
        pObject->Width=192;
        pObject->diameter=192;
        pObject->Height=192;
        pObject->pObject=pou;
        pObject->Type=SOD_OBJECT_TYPE_USEROBJECT;
        pObject->ReceiveEvents=PX_TRUE;
        pObject->Func_ObjectFree=SOD_Object_UserObjectFree;//回调事件
        pObject->Func_ObjectUpdate=SOD_Object_UserObjectUpdate;
        pObject->Func_ObjectRender=SOD_Object_UserObjectRender;
        pObject->User_ptr=play;
        pObject->impact_Object_type=SOD_IMPACTTEST_OBJECTTYPE_ENEMY;//碰撞类型
        pObject->impact_test_type=0;//碰撞测试类型

        pou->beAttackElpased=0;
        pou->life=life;
        //绑定动画集
        PX_AnimationCreate(&pou->user_animation,PX_ResourceLibraryGetAnimationLibrary(&play->runtime->runtime.ResourceLibrary,SOD_KEY_ANIMATION_USERDEF));
        PX_AnimationSetCurrentPlayAnimationByName(&pou->user_animation,SOD_ANIMATION_KEY_USERDEF_NORMAL);
        pou->velocity=PX_POINT(1,0,0);
        pou->atom_elpased=0;

        PX_ObjectRegisterEvent(pObject,SOD_OBJECT_EVENT_DAMAGE,SOD_Object_UserObject_OnDamage,PX_NULL);
        return pObject;
}

然后玩玩看:不好意思做着做着灵感就突然来了,把贴图换一下,
侵删,谢绝律师函
文章的最后

你可以:
在这里下载到游戏和它的完整源代码:
matrixcascade/PainterEnginespace old driver的代码就在对应名字的文件夹,别忘了:
下载后你可以在这里找到到游戏的Release版本,直接运行就可以了,到手直接开玩(避免律师函警告没发cxk版本)
或者:
加入PainterEngine交流QQ群:419410284,欢迎游戏开发者和C语言玩家加群交流,谢绝作业党伸手党,你可以在这里咨询关于游戏和PainterEngine的问题
附上笔者刚刚的战绩
欢迎点赞充电:
https://www.bilibili.com/video/av57796335/

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-3-17 19:27 | 显示全部楼层
看这个Youtube频道,2d横版像素游戏的制作流程及代码编写,你想知道的一切知识在这里都有!
https://www.youtube.com/c/NatWeiss
他的twitter是:https://twitter.com/wizard_fu
这是他的个人网站,介绍cocos2dx游戏开发的:
https://wizardfu.com/book/cocos2d-x/best-free-game-engine/
发表于 2021-3-17 19:31 | 显示全部楼层
这样,我有一个大胆的想法。
我从今天(10月17号)开始,从零起步制作一个 2D 横版像素游戏。暂定为期一个月,中间的制作过程就更新在这个答案里。
过程中,如果有什么比较好用的教程、资料,也一并更新于此。
一个月后,告诉你完整的「一个2D横版像素游戏的制作流程」。
(对了,使用的引擎是 Unity3D)


最终的成品,做到哪儿算哪——不要期待太高,但争取能完整地过一遍基础流程。
(其实对于一些有经验的人来说,一周末的 Game Jam 就够完成这个目标了。但我很久没碰游戏引擎,许多坑都得从头踩起,所以放宽些时间吧。)
在那之前,就先保持着匿名。
2017年10月17日 02:14:30 开搞
呼,看了楼上的精致回答有点虚。顺便也感谢经验分享!
不过我接着做我的……
刚才试着画了个角色动图。是不是主角?不知道。可能只是背景君吧。
严格来说并不算「像素」……虽然是用的PS里的铅笔工具。随手涂的太糙了。
打算是先凑合着准备一波资源,把重心放在开发过程上。
那,就酱先。
2017年10月17日 02:39:23
赞数涨的有点儿厉害啊……果然游戏是热门话题。
其实我不建议你们看到这么一个吹大话的答案就先给点赞的。万一我这是随口立 Flag 你们不就赞错人了吗。


说回正题。
今天开始试着画了一下场景图。这个阶段是最开心的啦,还没开工,没太多限制,可以随便开脑洞设想做什么样的游戏。边画边能构思「诶这个可以这样出来」、「这里主角可以那样」、「最后巴拉巴拉」——这种随意放飞阶段可以说是游戏制作过程里最开心的事儿了!
至于实现过程嘛……哈哈哈其实是长期打磨的辛苦活,倒未必有多少欢乐的。
脑洞中的场景构想图
可以看到,之前画的那个蠕动怪,暂时是设定成 BOSS 一类的角色。NPC 也说不定。
主角是左下角那个黑色的小球。加了呆毛,但感觉对动画来说可能会有不小的工作量,之后也许会删掉也说不定。
亮黄色个部件是用来给自己指示的,黄色矩形是主角的大小设定(40*40),橙色的是跳跃的高度(100+60),也就是二段跳最高跳到橙色的顶端,正好能跳上右边平台1号。
嗯,我自己玩横版游戏很喜欢二段跳的,所以做的游戏肯定也要有这个功能啦。
两侧那些线条是避免画面太空旷而加的前景来着。之后应该会随着镜头移动而一起晃动吧,增加景深。


今天想到的关于这个游戏的「参考游戏」,一个是《Wuppo》还有一个是《The End is Nigh》;后者我没玩过,但是想试一下它一个关卡只包括在一个屏幕内 的思路;前者嘛……比起近战攻击,射击战斗好像比较容易实现的样子。
先说到这儿,接下来要想办法在 Unity 里搭建出这个场景啦!
对了,说一个奇技淫巧。在 PS 里设定图像差值为 邻近,然后把图片缩小几倍再放大回原尺寸,能有效增加「像素感」(其实就是锯齿啦,哈哈)
昨天画的那坨就做了这么个操作,放大看还挺明显的。不过这是我偷懒才只么干的,正儿八经的艺术家还是应该手动点像素哈。
感谢 @十八 提供的情报!
缩小1/5再放大之后的效果
2017年10月17日 22:57:01
刚才折腾了一下怎么从 PS 里导出帧动画,这里也分享一下设置。
为了让导出来的图像素材正好是帧的数量,所以把所有帧的时间间隔设为 1 秒,然后把帧速率设为 1 FPS;如果想要导出透明底色的图片,选择 PNG 格式,右下角 Alpha 通道选择「预先进行白色正片叠底」。
参考:ps 如何批量导出动画所有帧?
2017年10月17日 23:25:02
那,开始在 Unity 里捏小人儿了。经过一番自己夏姬八折腾,给角色加上了 Animation,抖起来很鬼畜。你看到的 GIF 效果是减速过的,游戏里刚启动出来是 60fps 的狂暴颤动效果。
好,那今晚先到这儿,明天继续搭场景+捏小人儿~
2017年10月17日 23:59:56
没忍住,接着调了点儿,现在可以从天而降了。
2017年10月17日 00:12:11
好久不见!我又来更新啦~咕咕了两天不好意思嘿。
昨天在亚马逊上买了两本书,今天就到了。都是神往已久的游戏设计方面的书籍。《游戏设计艺术》和《游戏设计梦工厂》,算上优惠平均下来每本只要60元噢。
说正题,今天试着实现了“子弹”的雏形。因为主角工程量会比较大,留到之后再做。先尝试做出「子弹打在敌人身上,闪现红光,敌人扣血,子弹消失」的功能吧。
然后目前完成的是……「创建超多子弹」,哈哈。
Unlimited Bullet Works——无限弹制!
倒放是 GifCam 里实现的,并不是游戏内效果。但是看起来很有意思,之后也想办法用代码实现这个功能吧ww
2017年10月20日 23:59:39
希望子弹是全方向的,所以做了这个功能。现在可以朝整个屏幕内随意开火啦!
参考资料:[Unity C# Tutorial] : 360° Aiming in a 2D Platform.
2017年10月21日 14:07:05
咕咕咕……!
向组织汇报一下……
吹完牛的四个星期里,一个周末加班,两个周末玩儿《刺客信条·起源》,最后一个周末玩儿《马里奥·奥德赛》。
所以,我理解为啥一个做游戏的前辈说他自己很少玩游戏了。
一旦玩了别的好游戏就很难抽出时间(和足够的意志)去做自己的游戏了啊!!
2017年11月20日 08:33:34

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-3-17 19:39 | 显示全部楼层
1 软件

Piskel, 免费但是只能在线用浏览器使用, 它的动画功能非常强大, 所以强烈推荐
Pyxel Edit, 一次性付费 9.9 美元, 可以快速编辑 Tile, Tile 这个概念会在后面做解释.




Aseprite, 目前比较主流的像素风格绘制软件, 15 美元




2 线条和图形

直线




像素艺术中, 水平线和垂直线很简单, 关键的是斜线, 斜线在于像素的长宽比例(ratio), 如上图中, 1:1 的比例构成了 45 度的线条.




2:1 的线条构成了较平缓的线条, 同理, 1:2 的线条要陡峭许多
曲线




上图所示是一个初学者在画曲线时比较容易犯错的例子,在视觉上并不是很平滑, 因为在很多转角处出现了"直角"的形状, 所以需要把他们"破掉".




这是修改过后的版本, 可以看到曲线的感觉更明显同时也更干净了.
形状
所谓形状就是封闭的线条.




如上图所示,左图仍然是线条, 右图既是图形(正方形).








难度比较大的是圆形, 1:1 和 2:2 的就是个正方形, 因为没有多的像素可以用来切割
3:3 的就是一个正方形去掉了四个角的像素, 同理可以推到4:4和5:5的, 但是到6:6的的时候, 如果只是去掉四个角的像素, 看上去就还是像一个正方形, 所以需要进一步切割, 如上图
3 明暗和灯光





想要让一个平面图形有体积感, 首先需要知道五大基础几何图形, 他们分别是正方体, 球体, 圆柱体, 圆锥及三角体. 世界上的大部分物体可以简化成这五个几何图形, 比如人的胳臂是圆柱体, 一个苹果或者橙子是球体.




通过加上暗部,明部, 中部,明暗交接处, 高光, 我们可以把平面的图形做出有体积的效果.




以正方体为例, 首先选择暗部和明部的颜色并进行填色, 中部保持原来的色彩不变(方块左侧)




难点在于高光和明暗交界处, 由于光来自左上方, 所以可以看到原来 黑色的边被最亮的高光替代了,因为那个边离光源最近. 同理离我们最近的那条边也会受光源影响变浅, 最后右边的明暗交接处的边的颜色用中部的颜色替换即可.
4 颜色和材质

颜色




和大多数图像编辑软件一样, 像素风格也有自己的颜色编辑器, 比如像 RGB, HSV 等等.




在给一个图形上色时, 首先需要确定它的明暗关系(黑白关系), 然后即可确定它的色盘(色彩关系), 然后用相应的颜色替换黑白的区域即可.
材质
想要做出有说服力的材质需要上网找大量的参考, 下面以木板材质的制作流程为例:








仍然遵循给立方体上色的原则, 首先确定大的色彩关系和底色, 然后依次加强暗部, 明部, 高光,可以根据情况加入更多的颜色, 颜色层次越丰富, 可信度也就越高.
5 Tile 的概念





什么是 Tile? Tile 可以理解为一个画面中的单个元素, 上图是游戏塞尔达中的一个截图, 其中有一些很显而易见的 Tile, 比如像木桩, 草和花, 波浪等等,




一般来说一个 Tile 是 16*16 像素大小, 也有一些长方形的 Tile, 比如 16*8 像素,通过复制 Tile, 我们可以迅速创建出一个地图.
但是在大量复制 Tile 的时候, 也要考虑到画面的多样性, 过于单调的重复元素会让人审美疲劳, 所以在很多时候, 也需要制作不同的 Tile 来破掉这种单调感, 以草地的元素为例:




6 背景

在绘制像素游戏背景的时候, 通常我们遵循的原则是, 离我们越近的部分, 细节越多, 色彩对比越强烈, 离我们越远的部分, 细节就越少,从而实现景深的效果.
以下图为例, 最近的部分可以看到草的细节, 而最远处的山慢慢和天空的颜色融合在了一起








但在关卡设计中, 关键性道具, 或者是剧情类地点需要突出显示, 如上图的大树.


7 角色基础



8*8 像素
8*8 像素角色是最简单的一种, 因为只有 64 个像素可以使用, 所以需要重点突出角色的脑袋, 此时身体比例已经不重要了,腿和胳膊只需要 1 到 2 个像素点来代替




16*16 像素
16*16 像素角色可以拥有一个正常的比例(7-8 头身), 因为头部只有四个像素, 所以无法添加眼睛(需要至少一个像素作为间距), 但是 16*16 我们可以加上更多衣服的细节了,或者是一些比较具体的装备. 当然我们也可以走 8*8 的大头风格, 只是这次我们在衣服的设计及人物的细节有了更多的选择








32*32 像素
32*32 是 1024 个像素, 这个时候你的可控性就多出非常多了, 像素越多, 细节也就越丰富




8 动画

像素动画和普通的 2d 传统动画一样, 遵守 12 条动画原理, 这里就不一一赘述, 感兴趣的同学可以自己去百度一下.




以下面这个小矮人砍木头为例, 不需要斧头在空中的每一帧都画出来




可以只画第一帧和最后一帧, 中间用"smear frame"代替, 中文就叫动态模糊帧好了, 如下图所示




通过增加首帧和尾帧的帧数, 可以增加斧子的体量感








9 横板平台制作

假设我们现在要制作一个马里奥式的跳跃式关卡游戏的话, 首先通过前面学到的 Tile, 我们可以迅速制作出几个平台作为备用, 以供角色进行行走和跳跃.






接下来是简单的背景制作, 要注意横板游戏过程中的背景不要弄得过于花哨, 点到为止即可, 不要抢了前景的风头.




前景可以适当加一些细节, 如树木和花草




游戏主角的制作需要注意的是, 颜色需要符合世界的设定, 不要太格格不入, 另外人物大小同样也需要刚刚好, 如果过大或过小在跳跃平台的时候就会显得过于简单或困难.
一般来说, 主角的外轮廓会用对比性比较强的黑色加强, 这样利于视觉上的一个定位




游戏里敌人的一个制作, 同样, 用色尽量用世界里的颜色, 外轮廓黑边加强




最后, 完善一个平台的设计, 可以加入一些陷阱以及像梯子这样的道具.


本文作者 —— Carter老师



主修电脑动画和视觉特效,拥有丰富的一线影视项目经验,擅长电脑动画和电影视效的专项教学,2D到3D的跨学科指导。熟悉国外的影视产业环境,对于国外A类大片制作流程有很深的理解.曾参与制作华特迪士尼、漫威、索尼、Netflix等国际大厂项目, 有丰富的教学经验。
项目经验
WandaVision旺达幻视、MonsterHunter怪物猎人、Nightmare Alley、Shadow and Bones、The Old Guard永生守卫、Ad Astra星际探索、Call of the Wild野性的呼唤、Bad Boys For Life绝地战警:极速追击。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 15:51 , Processed in 0.142397 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表