创造一个在线斗地主游戏:从零到一(第二部)
原文:从零到一,撸一个在线斗地主(下篇) | AlloyTeam
作者:TAT.vorshen
上篇回顾:我们说了斗地主游戏的渲染展示部分,最后也讲了下canvas中交互的情况,下篇的重点就是游戏逻辑。
逻辑主要分成两块:流程逻辑和扑克牌对比逻辑。
github地址:github.com/vorshen/lan…
流程逻辑
分析
这里流程上的逻辑分为两部分,一个是场景切换,还有一个就是房间页中游戏进行的流程
先简单说下场景切换,我们这个斗地主游戏有如下三种场景切换
- 首页 -> 大厅页
- 大厅页 -> 房间页
- 房间页 -> 大厅页
我们这里偷了懒,首页和大厅页没有用canvas,直接上了dom,写起来也很奔放,没有用框架。如果游戏想正式一点,千万不要这样。看起来首页和大厅页逻辑很简单,那是因为我们漏掉了很多点(时间真的不够。。)。
用一张图表现一下,我们漏掉的点:
在我们如此简化的背景下,如果说还有什么需要注意的,可能就两点
1、是否提前加载模块
比如当我们进入首页的时候,要不要把大厅页和房间页都初始化完毕?
这里我没有选择初始化,一定是真正使用到才会初始化。理由主要就是后面用到再初始化的开销并不大,可以接受。
如果当遇到,某一个场景很复杂,切换需要较大的开销,可以考虑提前进行一些初始化的工作。
2、销毁是真销毁还是隐藏
大厅页和房间页存在来回切换的情况,当发生大厅切换到房间的时候,可以选择将大厅页隐藏,也可以选择将大厅页销毁,后面用到再初始化。
这里我们选择只是将页面隐藏,也就是说当房间页第一次展示的时候,需要进行初始化(较大开销),以后再展示,就是很少的性能开销了。 大概代码如下:
/**
* 房间展示,主要是生成stage
* @param info
*/
private _show(info: i_RoomShowOptions) {
this._roomId = info.roomId;
if (this._inited) {
// 初始化过了,stage肯定初始化过了,直接展示
this._stage.show();
} else {
// 第一次展示,初始化stage
this._initStage();
this._inited = true;
}
……
}
具体代码在Hall.ts和Room.ts中
因为我们页面简单而且小,常驻的话对性能影响不大,如果打算常驻的页面展示率低或者隐藏运行也很占用资源,那还是推荐把真的干掉。
房间中流程
消息驱动
首先我们认为在房间中,流程的变化都是事件驱动,具体可以看下图:
注意:右侧如果有箭头,意味着可能该阶段自己切换到该阶段(只是该阶段主角玩家发生变化)
在每个阶段,前端只能有对应的操作。那么每个阶段的切换,事件的发起者是谁呢?写代码的时候,我发现可以有两种模式进行阶段切换。
1、前端控制
以「叫地主阶段」->「抢地主阶段」为例,首先前端肯定知道游戏的轮转顺序(必须知道,因为布局就得考虑),轮转顺序是逆时针的。
当服务器下发一条「xx叫地主的消息」后,前端可以知道
- 接下来要进行抢地主阶段了
- xx的下一个是yy
那么前端可以主动将状态转为「yy进行抢地主状态」。
这个没有问题,逻辑上也讲得通,而且乐观UI的思想,能让用户最快的感知到变化,理论上体验最佳。甚至!可以节省与后台的传输,因为后台只需要下发「xx叫地主」,都不需要下发「yy进入到抢地主状态」了。
不过情况不是这么简单……写代码过程中发现了些问题。
「叫地主阶段」->「抢地主阶段」没问题,走的通;「抢地主状态」->「抢地主状态」也没问题,走得通;「抢地主阶段」->「出牌阶段」怎么办?
我们可以在前端将每个玩家叫地主、抢地主的结果记录下来,然后保证和后端一样的逻辑,也可以得到这局游戏的地主是谁。但是地主获得的三张牌呢?这是一定要得从服务器获得的,出现了冲突,或者说前端无法完整实现的地方。
更明显的还有「准备阶段」->「叫地主阶段」,前端完全不知道谁是叫地主的,因为这个可能不按轮转顺序来。
到这里,**是不是我们也可以前端+后台配合的方式?**尝试了一下,并不好,这种组合的形式让代码变得难写,我不推荐这种方式。
不过也不敢保证,也许是我写法上的问题,如果对这里有建议和想法,可以一起讨论。
2、后台控制
所以我最后采用了后端精准控制的方式,一切都是以后台下发为准。
我选择「叫地主」之后,理论上可以将前端状态转到「下个人抢地主」,但是并没有,我一定得等到后台状态变化的消息才进行转换。
注意:但是按钮,还是得提前反馈啊,否则网络延迟会让用户抓狂的。
所以房间逻辑这里,整个流程,是靠后台消息进行驱动的。代码大概:
private _addMessageListener() {
// 对手进入
this._app.network.addEventListener('Room.PlayerEnterRoom', this._playerEnterRoom);
// 对手离开
this._app.network.addEventListener('Room.PlayerLeaveRoom', this._playerLeaveRoom);
// 监听玩家准备
this._app.network.addEventListener('Room.PlayerReady', this._playerReady);
// 进入叫地主阶段
this._app.network.addEventListener('Room.EnterAskLandlord', this._enterAskLandlord);
// 对手叫地主
this._app.network.addEventListener('Room.PlayerAskLandlord', this._playerAskLandlord);
// 进入抢地主阶段
this._app.network.addEventListener('Room.EnterGrabLandlord', this._enterGrabLandlord);
// 对手抢地主
this._app.network.addEventListener('Room.PlayerGrabLandlord', this._playerGrabLandlord);
// 游戏开始
this._app.network.addEventListener('Room.GameStart', this._gameStart);
// 出牌
this._app.network.addEventListener('Room.PlayerShotPukes', this._playerPukes);
// 继续出牌
this._app.network.addEventListener('Room.LoopPukes', this._loopPukes);
// 游戏结束
this._app.network.addEventListener('Room.GameOver', this._gameOver);
}
具体代码在Room.ts中
客户端同步
稍微延伸一下,刚刚说的那种情况,很类似游戏中,客户端同步的两种方式:帧同步和状态同步
帧同步(行为同步)
帧同步的核心就是 不同的客户端 + 相同的输入(行为) = 相同的输出(状态)
如果能一直保证这个公式成立,那么服务器只需要推下发行为,无需下发状态,行为的开销肯定远远小于状态,优势在于性能。这一般用于实时性要求高的游戏中,比如格斗类、fps类游戏。
状态同步
状态同步就好理解了,客户端以服务器下发的状态为准,客户端就像一个播放器一样。这种优势在于服务器掌握绝对控制权,一般用于实时性要求不高的游戏中。
与服务器对接
游戏和传统web开发在网络上的差距也是很大的,传统web开发,资源加载完毕后,也就是cgi拉取一些数据或者上传一些数据会与后台对接,总而言之就是前端与后台的交流并不密切。
但是游戏不一样,游戏是需要频繁交换数据的,而且必须要有后台主动推送的能力。斗地主这款游戏算是上行很少的游戏了,理论上其实cgi+长轮询也能满足我们的需求,但是现在websocket这么好用,不可能不用啊。
websocket
websocket这里我们也是裸写的,没用开源的库,也没写重连啥的逻辑,如果在线游戏想正规一点,一定要考虑重连啊。
如果还不了解websocket的同学,可以找介绍看下,很简单。
但是websocket也有尴尬的地方,主要有两点:
- 下行消息一个通道,没有回调的概念
- 无法携带session
先说1,我们用websocket进行send调用,调用就调用了,没有回调函数的概念的。后台如果想针对我们的请求进行回报,也得走统一的下发消息,对于前端来说,就是触发了onmessage。
这样肯定是不行的,既然底层不支持,我们就得进行一次封装,其实核心就是版本号控制一下。
原理如下图:
我们发送消息的时候,如果有回调函数,就会记录一下(自增id标示),然后这个自增id会发送给后台。
后台下发消息的时候,有两种,一种是带着回调id,如果发现是这种消息,就拿着id去回调函数池子里面找到对应的函数执行。如果没有回调id,意味着是单纯的推送,对应执行。
大概代码如下,具体代码在Network.ts中
class Network extends EventDispatcher {
// 收到服务器下发消息
private _processMessage(msg: any) {
// response消息
if (msg.id) {
let cb = this._callbacks[msg.id];
delete this._callbacks[msg.id];
if (typeof cb !== 'function') {
console.error('callback is not a function for request: ', msg.id);
return;
}
cb(msg.body);
return;
}
// 服务器推送消息
let route = msg.route;
if (!route) {
console.error('no route in message');
return;
}
this.dispatchEvent(route, msg.data);
}
// 想服务器推送消息
notify(msg: any, callback?: Function) {
if (!this._ws) {
return;
}
if (typeof callback === 'function') {
msg.id = ++this._callbackIndex;
this._callbacks[msg.id] = callback;
}
this._ws.send(JSON.stringify(msg));
}
至于无法携带session,这个就没办法了,只能相当于每次手动将uid带上去,服务器会根据uid拿到用户信息。
扑克牌对比逻辑
到了斗地主最核心逻辑部分了,那就是扑克牌大小的对比,也是我们使用webassembly的地方。
webassembly
对不了解webassembly的同学先简单介绍一下webassembly,可以理解为:将其他的语言(比如C++,go,java等)写的代码,跑在浏览器上。其他基础知识就不在这里提了哈,可以自行查阅。
外界看好wasm的优势在于快!虽然js有v8,但是相比较那些静态语言老流氓们,还是有些差距的。目前wasm应用场景最多的应该在于音视频的解析、字符串操作、大量数学计算等一些高cpu操作上。
我觉得wasm不仅仅有速度上的优势,还有代码复用这个被忽视的特性。在游戏上,这个特性帮助会很大。
以我们这个斗地主为例,核心部分是扑克牌对比逻辑。这个逻辑,前端要用把,判断是否可以出牌的时候,如图
但是后端不能无脑信任前端的牌吧,后台也必须得校验一次。一份逻辑,写一次总比写两次好吧,况且还是一个比较复杂的逻辑。wasm的出现解决了这种场景的痛点,主要也是游戏开发中,这种情况也比较多,很常见的就是碰撞检测。
具体一份代码是怎么用的,我们稍后再说,我们先把扑克牌对比的逻辑用C++写出来,否则其他都是白搭。
如何对比
因为比较简单,我没有去网上搜实现,自己写了一套,目前看来应该没啥问题,是不是最优思想不清楚。原理如下
我们先把扑克牌分类一下,如下图:
对应的枚举:
enum PukeType {
ERROR, // 无法匹配
EMPTY, // 空张
SINGLE, // 单张
DOUBLE, // 对子
THREE, // 三不带
BOOM, // 炸
THREE_SINGLE, // 三带一
THREE_DOUBLE, // 三带二
DOUBLE_ROW, // 连对
THREE_ROW, // 连三不带
THREE_SINGLE_ROW, // 三带一飞机
THREE_DOUBLE_ROW, // 三带二飞机
};
这里「炸弹」是比较特殊的,因为它可以和其他类型进行大小比对,其他类型,必须相同类型进行对比,可以理解为对2也打不过一单张3
因为类型多,看起来同类型对比复杂,其实并不是,因为同类型对比,核心比的是某一单张牌。
- 3带1/2,比的是3张中的牌谁大
- 连对,无论连了几对,比的是最大的那对中的牌谁大
- 炸弹,其实比单张
- 其他的就不罗列了,其实都是这样
那么我们就可以这样
- 格式化传入的pukes
- 得到pukes的类型 和 这个类型下,能代表最大的那张牌
- 除了炸弹,如果类型对不上,认为比不过
- 类型一样,比核心牌
代码如下,具体代码在puke-compare.h中
/**
* 对比两组牌的大小
*/
bool PukeCompare(std::vector<Puke>& pukesA, std::vector<Puke>& pukesB) {
// 先格式化两组牌
Parse(pukesA);
Parse(pukesB);
// 分析牌的类型
PukeCompareResult bResult = GetCore(pukesB);
PukeCompareResult aResult = GetCore(pukesA);
// 不合法,直接认为出牌小
if (bResult.type == PukeType::ERROR) {
return false;
}
// 如果本身牌为空,则也认为出牌小
if (bResult.type == PukeType::EMPTY) {
return false;
}
// 对比的牌为空,则认为出牌大
if (aResult.type == PukeType::EMPTY) {
return true;
}
// 一方是炸弹,另一方不是炸弹
if (bResult.type == PukeType::BOOM && aResult.type != PukeType::BOOM) {
return true;
}
if (bResult.type != PukeType::BOOM && aResult.type == PukeType::BOOM) {
return false;
}
// 如果类型不一致,也认为小
if (bResult.type != aResult.type) {
return false;
} else {
// 类型一致,比核心牌
return (pukesB[bResult.core]) > (pukesA[aResult.core]);
}
}
格式化牌和分析牌类型这两块,也不复杂,稍微有些细节,感兴趣的话可以看,代码都在puke-compare.h中
js调用c++函数
代码写完了,服务器端ok了,我们就得让前端能跑起来C++的代码。借助emscripten,其实调用起来也挺方便的,这里没有时间和篇幅说具体怎么弄的,但可以说的抽象一些。
js调用C++代码有两种方向
一种是直接调用C++函数
还有一种是在js环境下,new出C++对象,这个不好画图,我就不画了哈
二者的区别主要也是写法上的区别,只调用函数的方式控制力较弱;new对象的方式,控制能力强,但是如果设计的不好,容易玩坏,而且麻烦些。
注意要考虑垃圾回收,在js侧new出来的C++对象,v8可不会帮你垃圾回收,得自己实现一个简单的引用计数的垃圾回收(代码在my_glue_wrapper.cpp中)。所以说,如果选择new对象的方式,一定要考虑周全。
我们这里相当于两者结合使用了,毕竟本来就是为了练手,涉及到webidl相关的知识(将C++对象,转换为js可以理解的对象)。具体代码在assembly下puke.idl和my_glue_wrapper.cpp中
webassembly这里,本来打算多写点,但是发现不好下手,如果写的详细,内容会较多。感觉又能开一篇文章了,但最近实在是比较忙,能抽出空写这两篇已经到极限了……不过现在网上webassembly相关的文章资料已经很多了,感兴趣的同学可以带着一起看,应该就很有助于理解了。
思考
写这个游戏期间,因为不同于平时业务开发,只考虑自己前端的那部分,这次从产品到前端后台都是一个人,有一些非前端的感触。
- 产品流程图很重要,能提前理清楚一些逻辑坑点,防止无脑撸代码然后返工。这里吃了不少亏
- 设计大大们是真的牛皮
- 联调过程保证后端稳定性,尽量少改代码了,后台重新编译、重启的成本高很多
- 时间关系,没有弄单元测试,但能准备单元测试,还是要准备,很重要
- 扑克对比,是否可以引用配置的方式,这样就可以很好的支持其他扑克模式的对比了
结尾
终于到结尾了,感谢阅读到这里的同学。这个游戏本来是一个无心之作,不过也起到了练手的作用。
两篇文章更侧重于思路和宏观的一些东西,加上可能一些小坑。斗地主算是一个简单的游戏,但是我低估了他完成基本闭环需要的时间,所以很多地方都在赶,如果发现有写的不好的、考虑的不好的地方,欢迎斧正~
大家一起交流沟通~
AlloyTeam 欢迎优秀的小伙伴加入。
简历投递: alloyteam@qq.com
详情可点击 腾讯AlloyTeam招募Web前端工程师(社招)
推荐阅读
-
创造一个全新的在线斗地主游戏经验 (上篇)
-
创造一个在线斗地主游戏:从零到一(第二部)
-
NeurIPS 2022 | 最强斗地主AI!网易互娱AI Lab提出基于完美信息蒸馏的方法-完美信息蒸馏(PTIE) 在斗地主游戏中,非完美信息的引入主要是由于三位玩家均不能看到别人的手牌,对于任意一位玩家而言,仅可知道其余两位玩家当前手牌的并集,而难于精准判断每位玩家当前手牌。完美信息蒸馏的思路是针对这种非完美问题,构建一个第三方角色,该角色可以看到三位玩家的手牌,该角色在不告知每位玩家完美信息的情况下通过信息蒸馏的方式引导玩家打出当前情况下合理的出牌。 以强化学习常用的 Actor-Critic 算法为例,PTIE 在 Actor-Critic 算法的应用中可以利用 Critic 的 Value 输出作为蒸馏手段来提升 Actor 的表现。具体而言即在训练中 Critic 的输入为完美信息(包含所有玩家的手牌信息),Actor 的输入为非完美信息(仅包含自己手牌信息),此种情况下 Critic 给予的 Value 值包含了完美信息,可以更好地帮助 Actor 学习到更好的策略。 从更新公式上来看,正常的 Actor-Critic 算法 Actor 更新的方式如下: 在 PTIE 模式下,对于每个非完美信息状态 h,我们可以在 Critic 中构建对应的完美信息状态 D(h),并用 Critic 的输出来更新 Actor 的策略梯度,从而达到完美信息蒸馏的效果。 PTIE 框架的整体结构如下图所示: 无论是训练还是执行过程中智能体都不会直接使用完美信息,在训练中通过蒸馏将完美信息用于提升策略,从而帮助智能体达到一个更高的强度。 PTIE 的另一种蒸馏方式是将完美信息奖励引入到奖励值函数的训练中,PerfectDou 提出了基于阵营设计的完美信息奖励 node reward,以引导智能体学习到斗地主游戏中的合作策略,其定义如下: 如上所示,完美信息部分 代表 t 时刻地主手牌最少几步可以出完,在斗地主游戏中可以近似理解为是距游戏获胜的距离, 代表 t 时刻地主阵营和农民阵营距游戏获胜的距离之差, 为调节系数。通过此种奖励设计,在训练时既可以一定程度地引入各玩家的手牌信息(出完的步数需要知道具体手牌才能计算),同时也鼓励农民以阵营的角度做出决策,提升农民的合作性。 特征构建: PerfectDou 针对牌类游戏的特点主要构建了两部分特征:牌局状态特征和动作特征。其中牌局状态特征主要包括当前玩家手牌牌型特征、当前玩家打出的卡牌牌型特征、玩家角色、玩家手牌数目等常用特征,动作特征主要用于刻画当前状态下玩家的所有可能出牌,包括了每种出牌动作的牌型特征、动作的卡牌数目、是否为最大动作等特征。 牌型特征为 12 * 15 的矩阵,如下图所示: 该矩阵前 4 行代表对应每种卡牌的张数,5-12 行代表该种卡牌的种类和对应位置。 网络结构和动作空间设计 针对斗地主游戏出牌组合数较多的问题,PerfectDou 基于 RLCard 的工作上对动作空间进行了简化,对占比最大的两个出牌牌型:飞机带翅膀和四带二进行了动作压缩,将整体动作空间由 27472 种缩减到 621 种。 PerfectDou 策略网络结构如下图所示: 策略网络结构同样分为两部分:状态特征部分和动作特征部分。 在状态特征部分,LSTM 网络用于提取玩家的历史行为特征,当前牌局状态特征和提取后的行为特征会再通过多层的 MLP 网络输出当前的状态信息 embedding。 在动作特征部分,每个可行动作同样会经过多层 MLP 网络进行编码,编码后的动作特征会与其对应的状态信息 embedding 经过一层 MLP 网络计算两者间的相似度,并经由 softmax 函数输出对应的动作概率。 实验结果
-
41 个下载免费 3D 模型的最佳网站-使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 17. Clara.io Clara.io 是一个创建 3D 内容的全球平台,也是一个培养新 3D 艺术家的社区。Clara.io 提供+100,000个免费的3D模型,包括OBJ,Blend,STL,FBX,DAE,Babylon.JS,Three.JS格式,用于 Clara.io,Unity 3D,Blender,Sketchup,Cinema 4D,3DS Max和Maya。 使用说明:免费,标准和专业帐户仅供个人使用,如果您需要将 clara.io 用于商业用途,请与销售团队联系。 18. 3DExport 3DExport是一个市场,您可以在其中购买和销售用于CG项目的3D模型,3D打印模型和纹理。它提供15 +不同的3D格式供下载,如3DS MAX(.max),Cinema4D(.c4d),Maya(.mb,.ma),Lightwave(.lwo),Softimage(.xsi),Wavefront OBJ(.obj),Autodesk FBX(.fbx)等。它还提供15种不同的语言! 使用说明:免费下载仅供个人和非商业用途。 19. 3D Warehouse 3D Warehouse是一个开放的库,允许用户共享和下载SketchUp 3D模型,用于建筑,设计,施工和娱乐!任何人都可以免费制作,修改和重新上传内容到3D仓库,您可以找到任何您能想到的东西,如家具,电子产品,室内产品等。 使用说明:3D Warehouse中的所有模型都是免费的,因此任何人都可以下载文件以用于SketchUp甚至其他软件,如AutoCAD,Revit和ArchiCAD。 20. CadNav.com CadNav是CGI平面设计师和CAD / CAM / CAE工程师的在线3D模型库,我们提供超过50000 +免费3D模型和CAD模型下载。在CadNav网站上,您可以下载高质量的多边形网格3D模型,3D CAD实体对象,纹理,Vray材料,3D作品,CAD图纸等。 使用说明:免费下载仅供个人和非商业用途。 21. All3dfree.net 就像网站名称一样,它提供免费的3D模型,还包括Vray材料,CAD块,2d和3d纹理集合,无需注册即可免费下载。它是不断更新的,因此您可以查找或请求3DS,MAX,C4D,skp,OBJ,FBX,MTL等格式的模型。 使用说明:所有资源均不允许用于商业用途,否则您将承担责任。 22. Hum3D 自2005年以来,Hum3D帮助来自3多个国家的80D艺术家节省3D建模时间,并制作逼真的3D模型,用于电影,视频游戏,AR应用程序和可视化。所有模型均由首席3D艺术家进行验证,他们检查其是否符合专业要求和最新的3D建模标准。 使用说明:免费下载仅供个人和非商业用途。 23. Artist-3D.com 艺术家-3D 库存的免费 3D 模型下载按通用类别排序。它为人体解剖学、汽车、家具、火箭、卫星等模型提供 AutoDesk 3DS Max 格式。您还可以在浏览他们的网站时找到教程和类似类型的建模。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 24. Free the models 就像本网站的标题一样,它为3d应用程序和3d游戏引擎提供免费的内容模型。您可以为您的任何项目找到许多有趣且有用的模型!它提供3ds,wavefront,bryce,poser,lightwave,md2和unity3d格式的模型。还有一个很棒的纹理集合,可以在您最喜欢的建模和渲染程序中使用。 使用说明:您从这里下载的所有内容都可以免费使用,除非它不能包含在另一个免费的网络或CD收藏中,也不能单独出售。否则,您可以在商业游戏,3D应用程序或渲染作品中使用它。您不必提供信用,但如果您这样做,那就太好了。 25. Resources.blogscopia 本网站由一家名为Scopia的公司创建。他们制作3D图像和视频,您可以找到许多为CGI工作的信息架构设计的模型,所有这些都可以在现实生活中使用。您可以免费下载它们,但是,如果您想一次下载它们,您可以支付 3 到 9 欧元。 使用说明:您可以免费下载模型部分的所有文件。每个压缩文件都包含您也可以在此处找到的许可证。基本上,您可以对文件执行任何操作。唯一的限制是不归属于Scopia的重新分发。 26.ambientCG 1000+公共领域PBR材料适合所有人!环境CG是使用许多不同的方法和资产类型创建的,例如照片纹理(PBR),贴花(PBR),图集(PBR),照片纹理(普通),物质存档(SBSAR),雕刻画笔,3D模型和地形。您可以在所有项目中*使用它们! 使用说明:在 ambientCG 上提供下载的所有 PBR 材料、画笔、照片和 3D 模型均根据知识共享 CC0 1.0 通用许可提供。您可以复制、修改、分发和执行作品,即使是出于商业目的,也无需征得许可。信用将不胜感激。 不要满足于平庸的大理石纹理 - 立即使用我们的免费PBR大理石纹理升级您的3D设计。 27.Pixar One Twenty Eight 这是一个提供官方动画行业经典纹理的网站:皮克斯,创建于 1993 年,该纹理库包括 128 个重复纹理,现在免费提供。 它包含您来到的纹理,包括砖块和动物毛皮。肯定会有一些你可以使用的东西。 使用说明:皮克斯动画工作室的《Pixar One Twenty Eight》根据知识共享署名4.0国际许可协议进行许可。即使出于商业目的,您也可以重新混合、调整和构建您的作品,只要您以相同的条款对新创作进行信用和许可。 访问数以千计的免费纹理并提升您的设计游戏 - 立即开始下载! 28. 3DXO 即使有近 620 个免费贴纸可供下载,3DXO 也不是最大的资源,但它的内容非常有用,不需要注册。无论是简单的墙壁或地板,还是一些奇怪的小东西,您都需要的纹理都可以在此网站上看到。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 29. 3DModelsCC0 3DModelsCC0 与其他产品的不同之处在于它包含超过 250+ 个高质量 3D 模型,并且本网站上的所有内容都是免费的,完全是公共领域!使用我们的模型时无需信用或归属! 使用说明:为每个人提供完全免费的公共领域内容。 30.Sketch up texture club Sketchup Texture Club是一个非营利性的教育和信息门户网站,由3D社区的图像促进协会管理,特别强调面向学生和建筑和室内设计专业人士的可视化和渲染技术,以及所有正在学习3D可视化的人。 使用说明:您无需支付版税或使用费。纹理可以免费下载和使用。不允许将纹理作为竞争产品出售或重新分发,即使图像被修改也是如此。 31. FlippedNormals FlippedNormal 是一个提供计算机图形和 3D 资产的市场,您可以找到许多用于雕刻、建模、纹理、概念艺术、3D 模型、游戏资产或课程的高级资产! 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 32. NASA 3D NASA 3D网站是一个在线门户,提供与太空和各种NASA任务相关的大量三维模型和模拟。该网站是用户友好的,并提供有关每个型号的详细信息。该网站允许用户探索和下载几种不同格式的模型,包括 OBJ、STL 和 FBX,只需单击下载按钮即可。 使用说明: 要下载模型,只需单击模型页面上的下载按钮并选择所需的格式。 33. 3DAGOGO (Astroprint) 3DAGOGO 是一个提供广泛 3D 模型的网站,包括角色、车辆和建筑物。3DAGOGO 的独特功能之一是它专注于适合 3D 打印的模型,使其成为希望创建物理原型或模型的设计师的绝佳资源。要使用 3DAGOGO,设计师只需在网站上搜索他们正在寻找的模型类型,然后下载 STL 格式的文件。 使用说明: 要使用 3DAGOGO,只需搜索所需的 3D 模型类型并下载 STL 格式的文件。根据需要自定义模型,并确保在将其用于商业目的之前检查使用权限。 34. FreeCAD FreeCAD是一款了不起的3D建模软件,可让您在计算机上创建令人难以置信的3D设计。该软件可免费下载和使用,它提供了广泛的工具和功能,可用于创建用于各种目的的3D模型。 该网站易于浏览,您可以找到开始使用FreeCAD的所有必要信息。此外,该网站还提供一系列教程和指南,可帮助您了解 3D 建模的来龙去脉。 使用说明: 要下载模型,请访问网站并从库中选择所需的模型。该网站还提供了一系列使用该软件的教程和指南。 35. Pinshape Pinshape是一个提供一系列3D打印模型的网站。网站上提供的型号质量很高,因此您可以确保您的最终印刷产品看起来很棒。该网站提供了广泛的模型,包括从家居用品到小雕像和珠宝的所有物品。 但这还不是Pinshape所能提供的全部!该网站还允许用户上传和共享自己的3D模型。这意味着您不仅可以下载出色的模型,还可以通过分享自己的设计为社区做出贡献。此外,Pinshape 提供了一系列自定义选项,因此您可以调整和调整模型以满足您的特定需求。 使用说明: 要下载模型,请在网站上创建一个帐户,搜索所需的模型,然后单击下载按钮。该网站还为每种型号提供了一系列定制选项。 36.Yeggi Yeggi 提供了大量免费的 3D 模型,您可以下载各种格式的模型,例如 STL、OBJ 和 FBX。该网站易于使用,您可以按关键字、类别或特定网站搜索模型。 Yeggi 对于任何寻找 3D 模型的人来说都是一个很好的资源。它提供了大量的模型集合,从日常物品到复杂的机械,以及介于两者之间的一切。该网站的收藏量在不断增长,每天都有新的型号增加。 使用说明: 要下载模型,请在网站上搜索所需的模型,然后单击下载按钮。该网站还提供指向托管模型的原始网站的链接。 37. Open3DModel 来自开放3D模型的图像 Open3DModel具有各种类别的模型,包括建筑,车辆和角色。无论您需要建筑物,汽车还是人的3D模型,都可以在此网站上找到。 该网站易于浏览,您可以按类别或关键字搜索模型。每个模型都附带预览图像和详细信息,例如文件格式、大小和多边形数量。此信息可以帮助您选择适合您需求的模型。 使用说明: 要下载模型,请访问网站,从库中选择所需的模型,然后单击下载按钮。 使用最好的 3D 资产管理工具简化您的 3D 制作流程。立即试用它们,将您的 3D 项目提升到一个新的水平! 38. 3DExport 对于那些为其 3D 设计项目寻找 3D 模型、纹理和其他资源的人来说,该平台是一个很好的资源。该网站有大量模型可供选择,包括 3D 打印对象、游戏资产等。用户可以按类别、文件格式或价格范围浏览,以找到适合其项目的完美资源。此外,3DExport 还提供一系列教程和其他 3D 资源,以帮助用户提高技能并创建更令人印象深刻的设计。 使用说明: 要使用 3DExport,只需创建一个帐户并浏览可用型号。您可以按类别、格式和价格进行搜索,以找到所需的型号。找到喜欢的模型后,只需下载它并开始在您的项目中使用它。 39.Blend Swap Blend Swap是一个社区驱动的市场,提供与Blender软件兼容的各种免费3D模型。该平台允许用户共享和下载模型、纹理和其他资产,以便在他们的项目中使用。 使用说明: 创建免费帐户后,您可以浏览社区上传的大量3D模型。当您找到要使用的一个时,只需下载它并将其导入您选择的 3D 软件即可。 40. 3DShook 3DShook 是一个高级 3D 模型市场,提供一系列用于建筑、游戏等各个行业的高质量模型。该平台提供基于订阅的模型,具有不同的定价计划,允许用户访问一系列模型。 使用说明: 注册免费帐户后,只需浏览3D模型库,选择您喜欢的模型,然后以您需要的格式下载它们。 41. Smithsonian X 3D 史密森尼 X 3D 对于正在寻找历史文物和文物的高质量 3D 模型的设计师来说,这是一个独特的资源。该平台提供了大量3D模型,这些模型是根据史密森尼博物馆和研究中心中的真实物体扫描创建的。 使用说明: