让你的游戏支持MOD!
前言
对于一个游戏的开发者来说,让你的游戏支持mod是一件很酷的事情!但是,有时候我们会难以想象一个游戏到底要如何支持mod。通过自己的一番搜索后,发现网络上现有的游戏mod制作教程要么很零散,要么看不懂,根本无从下手。
本文章针对上诉这些,不仅从一个通俗、简单、易懂的角度来解决问题,而且还擅长举例子说明情况。
(注:本文使用C#与Unity来举例说明)
—作者CSDN小鸦子,转载须注明作者及出处
献给游戏开发者小白
- 本文章结构:
①mod支持低级版本—Json
②mod支持中级版本—C# Dll动态库
③mod支持高级版本—“简政放权”法
①mod支持低级版本—Json
Q1:什么是Json文件?
在制作游戏中,Json文件是帮助我们存储和读取游戏数据的一种文件格式。比如我们的游戏存档就可以是以一种Json文件的格式储存的。
那么既然是一个文件,那么它一定有属于自己的格式。
Json文件的格式如下:
{
"Name": "小明",
"level": 15,
"armor_id": [1,2,15,261,73],
"web": [
{ "name":"菜鸟教程" , "url":"www.runoob.com" },
{ "name":"google" , "url":"www.google.com" },
{ "name":"微博" , "url":"www.weibo.com" }
]
}
每个Json文件都以" { " (第一行) 开始,以" } " (第十行) 结尾,里面的内容以键值对的方式存储。
所以,每一个Json文件可以看成是一个字典(用键(Key)查找到唯一的值(Value),这样每个Key-Value称为键值对。例如Y = X^2这个函数,每个X且只对应一个Y,每个Key且只对应一个Value,因此Json文件里的Key不允许重复)。
第二行的 "Name"是一个Key,"小明"是一个Value,Key和Value之间要用一个:链接,如果不是最后一个键值对,请每个键值对之间加一个英文的逗号隔开。
Q2 :有了Json文件,游戏如何制作Mod?
假设我们做的游戏是一个刷装备游戏,那么我们需要很多装备的数据,我们有两条路可以走:
- 全写在游戏代码中,游戏代码就储存着这些数据
- 全写在Json文件中,游戏代码仅做读取这些Json文件
如果是我,我会选择后者。前者不仅难以维护,而且代码编写起来重复性太高,容易出错或失去耐心。
那么我们将所有武器数据都写在了Json文件当中。它就变成了这样:
{
"1": ["勇者的剑",10,2,0],
"2": ["勇者的弓",20,0,1],
"3": ["骨制斧头",15,1,1]
}
如果你武器Json文件储存在游戏根目录下的Data文件夹内,那么你就可以调用Unity内的函数读取该Json文件。详细参考
那么既然你能够创建这么一个武器Json并读取,那么其他游戏爱好者也可以创建和你类似的Json文件,并放在指定位置(如Mod文件夹内的Weapon文件夹内),然后通过你在Unity内编写代码读取这些数据,就可以达到支持Mod的效果。
- 遍历Mod文件夹下Weapon中所有文件的路径
- 然后通过Json读取函数逐一访问这些路径下的Json文件并读取Json数据
- 为了防止Json数据错误导致程序终止,必须得在游戏开始的时候加以验证Json数据的合法性,并对于不合法的数据应当抛出异常
②mod支持中级版本—C# Dll动态库
Q1:什么是DLL文件?
DLL是一个动态库文件,可以简单理解为代码的动态仓库。如果有C语言基础的应该知道,C语言编译的四大步骤是:预处理、编译、汇编和连接。
当程序通过这四大步骤编译下来成.exe文件后,该.exe文件的功能或者说是函数就已经静态确定了,就比如你定义了一个 int Add(int, int) 函数,在程序编译完成后,你再也无法修改这个Add函数的内容了,即Add函数已经静态确定。除非你将源代码内的Add函数进行修改,然后重新编译,才能使得这个Add函数发生变化,但新生成出来的.exe文件显然就不是之前那个.exe文件了。这就有点像房子盖好了就不能改大小了,如果要改大小那么请将房子拆了再建。
如果我们不想把程序重新编译,又想修改Add函数内容,那该怎么办呢?
- 方法就是DLL(动态库)的动态链接
我们只需要将Add写进动态库,然后在程序里面调用这个动态库的Add函数,那么当我们修改Add函数的内容时,我们只需要在动态库中修改Add函数即可,不需要把程序本身重新编译一遍。这就好像有一个房子叫Get_Money(收租,这是一个函数名字),前一段时间入住的是People A,而这一段时间入住的是People B,房主只需找到他的房子Get_Money,然后对里面的人进行收租就可以了。如果是采用非DLL编译的方式(也就是静态链接),那么当People A不住了、People B入住的时候,就需要将Get_Money房子拆掉,然后重新建一个Get_Money房子,这显然是很蠢的。
Q2:有DLL文件后,我们应该如何让游戏有更广的mod支持?
我们先来看Json的mod法,这其实局限性很大。当我们的游戏想实现魔法攻击的Mod的时候,常常就会实现不了。因为第一,游戏开发者没有开发魔法攻击,这使得我们只能用普通攻击模拟魔法攻击,但是一维的东西如何去模拟二维甚至是高纬度的东西呢?第二,游戏开发者只规定了金木水火土 这五种魔法攻击,其他的魔法攻击他并没有规定。那么作为游戏爱好者的你就很艹蛋
- 游戏爱好者:“游戏开发者你好,我想实现一个暗魔法攻击啊?怎么编写Json数据???”
- 游戏开发者:“额…给我点时间,让我将暗魔法加入游戏…”
这就是Json文件制作mod的劣势,只能在游戏开发者规定范围内开发游戏mod,而游戏开发者想到的东西是有局限性的,无法完美的适配游戏爱好者开发mod的各种条件。
因此,支持DLL开发游戏mod是很有必要的,这起码让你的游戏提高很大的一个档次。
DLL如何解决上述问题呢?非常简单!
(为了让各位能通俗易懂,我决定通过伪代码来进行讲解)
/*这是玩家类*/
class Player: MonoBehaviour
{
public String Name; //角色名字
public List<int> Attributions; //角色的属性值,包括当前血量、最大血量、各种攻击、各种抗性等等
};
/*这是敌人类*/
class Enemy: MonoBehaviour
{
public String Name; //敌人名字
public int ID; //敌人ID信息
public List<int> Attributions; //敌人的属性值,包括当前血量、最大血量、各种攻击、各种抗性等等
};
/*为了演示,就不继承类了*/
游戏开发者要做的:将玩家对敌人的攻击函数,从静态链接改为动态链接(从静态调用改为动态调用)
using UnityEngine;
using System.Collections;
using Attack; //引用Attack.dll
class cAttack: MonoBehaviour{
Player player = gameObject.GetComponent("Player");
Enemy enemy = gameObject.GetComponent("Now_Enemy");
void attack(Player player, Enemy enemy){
int hurt_num = player.Attributions[1] - enemy.Attributions[2];
if(hurt_num > 0){
enemy.blood -= hurt_num;
}
else{
enemy.blood -= 1;
}
}
private void Update(){
if(){
//attack(player,enemy);
Attack.attack(player, enemy); //执行Attack.dll内的Create函数
}
}
};
游戏Mod开发者需要做的:(例如要实现暗魔法攻击)编写玩家对敌人的攻击函数,然后根据自己写的Json文件数据来判断暗魔法攻击生效的值,然后返回。
public static void Attack(Player player,Enemy enemy){
//15为暗魔法攻击力,16为暗魔法防御力
int hurt_num = player.Attributions[15] - enemy.Attributions[16];
if(hurt_num > 0){
enemy.blood -= hurt_num;
}
else{
enemy.blood -= 1;
}
}
这样,即使游戏开发者没有想到要开发其他魔法攻击,但是也不影响其他魔法攻击的实现。
- 针对上面的案例,有人会问,如果Attributions的数组没有15那么长呢?或者15位置上正好和别人做的Mod冲突了呢?
第一问题解决方法:游戏爱好者自行维护一个Json文件,然后从这个Json文件里面读取player和enemy的各类属性值。
//伪代码,读取Json文件并将其转成字典类型
Dictionary<string, int> player_Attributions = Json2Dictionary(JsonLoad("mod\\player_Attributions.Json"));
Dictionary<string, int> enemy_Attributions = Json2Dictionary(JsonLoad("mod\\enemy_Attributions.Json"));
public static void Attack(Player player,Enemy enemy){
//15为暗魔法攻击力,16为暗魔法防御力
int hurt_num = player_Attributions["暗魔法攻击"] - enemy_Attributions["暗魔法防御"];
if(hurt_num > 0){
enemy.blood -= hurt_num;
}
else{
enemy.blood -= 1;
}
}
第二个问题解决方法:如果15位置上正好和别人做的Mod撞上了,那么这就是Mod冲突,这通常需要在游戏方面进行判断是否产生了Mod冲突。而游戏爱好者的解决办法则是如果发现MOD冲突,那么将15换成其他数字。
③mod支持高级版本—“简政放权”法
当你的游戏本体是以Mod的形式出现的,那你的游戏将会支持更多的Mod。
“简政放权”法的基本思路是:在游戏里开发游戏。
我们在Unity编写游戏代码的时候,通常是将代码写入Unity的九大回调函数当中。如果我们将这些代码写入一个DLL中(将尽量多的功能实现写在DLL,而将基本功能写在游戏内),然后在Unity回调函数中调用这些函数,并传入参数,就可以实现“简政放权”法实现Mod思路。
例如:战场上每过x秒(x由mod制作者定)生成一个敌人,这些敌人的任何东西都可以由mod制作者自行决定。
using UnityEngine;
using System.Collections;
using Create_Enemy; //引用Create_Enemy.dll
class Enemy_Create : MonoBehaviour{
float time = 0;
private void Update(){
time += Time.deltaTime;
//判断是否到达生成敌人的时机
if(Create_Enemy.canCreate(time)){
time = 0;
Create_Enemy.Create(); //执行Create_Enemy.dll内的Create函数
}
}
};
因此,Mod制作家们只需要在Create_Enemy.dll中编写好相对应的Create函数后即可,而且每个人制作的Create函数可以各不相同!
C#调用动态链接库DLL
注:编写过程中难免会产生错误,若发现错误可以与题主联系
2024-04-25 小鸦子