2D游戏引擎Labs2D开发记录 - 架构 - 游戏物体和世界

引擎架构:游戏物体和世界

游戏物体对于游戏引擎来说是一个非常基本的概念,也是主要的部分。

让我们先来思考一下一个2D游戏是怎样运转起来的呢?这是一个很有意思的话题,也很宽泛。
2D游戏的种类太多了,卡牌游戏是2D游戏、超级玛丽是2D游戏,但是你肯定知道卡牌游戏和超级玛丽是有区别的。因为卡牌游戏节奏十分缓慢,留给玩家足够的思考时间和暂停时间,而超级玛丽需要玩家的实时操作,是快节奏的游戏。在超级玛丽这个游戏中,你不断地通过几个按键来控制横版画面上的一个游戏角色,比如要控制好步伐来跳过障碍,技巧性的跳跃来踩死敌人等等。

但对于大多数游戏而言,不管是快节奏或者慢节奏,可以抽象成这两个概念:游戏状态游戏规则

  • 游戏状态是指在某一时刻游戏内的所有物体的数据,比如此时玩家的位置,怪物的位置,玩家的血量,怪物的血量等等。
  • 游戏规则是指在将当前游戏状态转入下一个游戏状态的方法。比如游戏中的物理法则,玩家受到按键操作作出的反应,生物碰到岩浆会掉血之类的。
    而游戏在电脑中就像一个模拟器,它会响应玩家的输入,不断地将当前的游戏状态转换成下一个游戏状态。

在游戏中,通常会有一个最小模拟时间单位,在每一个最小模拟时间单位下对应着一个游戏状态,到下一个最小模拟时间单位,游戏状态就会随着游戏规则改变。
比如经典的贪吃蛇游戏,我可以设最小模拟时间单位为t。最开始的游戏状态,贪吃蛇是一个两格的线条,出生在中央位置,而有一个食物随机分配在某个位置,贪吃蛇身上每个关节的位置、食物的位置构成了当前的一个游戏状态,此时的时间是0(因为才刚刚开始)。当时间到t时,游戏逻辑程序开始运算,将当前的游戏状态更新,具体怎么更新的方式就是游戏规则了。而这里的游戏规则就是贪吃蛇向所朝方向移动一格,若头部接触到食物,则尾部增长一格,若头部碰到身体的任意部位,游戏结束。当时间为2t时,同样的进行这样的运算,直到游戏结束为止。

这个案例十分简单,我相信可以让你理解我所说的内容。

用函数符号描述,游戏在逻辑层所做的事情,就是每过一个单位时间,将当前游戏状态G,通过一个游戏规则F,转化成下一个游戏状态F(G)。

现在开始切入正题!

游戏物体

在本引擎中,游戏物体以一个个Java实例的方式呈现。我通过 GameObject 类来定义一个 游戏实体 。一个游戏实体可以是游戏中的一颗树,可以是一个怪物,也可以是一个玩家。游戏实体在游戏规则驱动下的相互作用构成丰富的游戏过程。
但是每个游戏物体的特点和功能是不一样的,所以还有一个比较重要的概念,就是 游戏组件 ,在程序中用 Component 类来定义一个游戏组件。游戏组件代表了游戏物体的一个功能。而一个游戏实体则是游戏组件的集合,一个游戏实体可以选择不同的游戏组件来实现多样的游戏功能。
在引擎中,通过继承 Component 类来定义不同的组件。

总结如下:

  • 一个 GameObject 实例是多个 Component 实例的集合, GameObject 提供了增删组件的方法
  • Component 类在代码中是一个抽象类,不能实例化,定义了组件具有的一些基本方法,具体的游戏组件通过继承它来实现

最基本的组件是变换组件,代码中是TransformComponent类。这个组件定义了游戏物体的坐标,也就是x和y值,提供了对坐标进行变换的一系列方法(如平移、旋转、拉伸)。

如果我们想要让游戏物体具有现实中的物理效果,比如让许多小球进行碰撞,那么刚体组件可以实现它,刚体组件在代码中对应着RigidBodyComponent类,存储着刚体的详细信息,比如质量、形状等等。

现在先不急着把所有组件都介绍,我们先来观察一下。每个组件实例都存储了实现组件功能的数据,比如变换组件的物体坐标,刚体组件存储刚体信息。实际上,组件的主要内容是为了实现组件功能存储的数据信息,以及一些操作数据的方法,而真正的组件功能实现,也就是对应的游戏规则,则在另一个实例中实现—— 游戏系统

为什么要分离组件和系统呢?这里我用一个假设法。假设每个组件都有一个update()方法,代表着游戏规则,也就是每一个单位时间怎么处理这些数据,那么就出现一个问题,各个游戏实体的逻辑像是独立的,而实现相互作用则变得困难。比如刚体组件的物理效果,你首先要有一个所有刚体的列表,遍历这些刚体,计算哪些刚体接触在一起,接触之后计算两两接触后的新的速度和方向,这就需要一个整体的运算。整体上的操作,更加有可控性和灵活性。

游戏系统在程序上有一个抽象的基类 BaseSystem ,实现具体的游戏系统必须继承它。比如对于刚体组件,就有专门的物理系统(在程序上对应PhysicsSystem类)来接管和逻辑运算。游戏的循环逻辑运算,主要集中在一系列游戏系统上。一个系统可以接管一种或多种组件,也可以进行单独的抽象的运算。
BaseSystem类有一个抽象方法update(),在每个单位时间内它都会被调用。

这里举一个例子,如果你要实现一个游戏中的一个点,它的游戏规则很简单,就是不停的向右移动,怎么实现呢?
首先,我们先创建一个游戏物体,代表这个“点”:

GameObject point=new GameObject();

我们现在有了一个“点”实体,因为它有坐标,那我们为它添加一个变换组件:

TransformComponent component=new TransformComponent(0,0);//设置坐标为(0,0)
point.addComponent(component);

现在它有了变换组件,那么它具有了坐标的属性和相关能力了,为了让它向右动起来,我们要创建一个游戏规则:

public class PointRule extends BaseSystem{
  private TransformComponent point;
  public void setPoint(TransformComponent _point){
    point=_point;
  }
  public void update(){
    point.translate(1,0);//表示平移向量(1,0),也就是横坐标+1
  }
}

我们再把这个点加入到这个游戏规则中:

PointRule rule=new PointRule();
rule.setPoint(point);

是不是很简单?
不过要它实际动起来,光有这些代码是不够的,这个例子只是让你更好的理解我上面所说的东西。

游戏世界

游戏世界是游戏物体的一个容器,光有游戏物体是不够的,还要有一个统一管理和存储这些游戏物体的主体,代码中用GameWorld类来实现游戏世界。
容易想到的。GameWorld可以添加和删除GameObject实例。
不过,GameObject在添加到游戏世界之前,它是一个只具有数据的静态的实体,不会运动,也不会执行任何游戏规则。你可以先将一个游戏物体设定好属性之后,再加入游戏世界,不过也可以加入世界之后动态改变它的属性。

游戏世界既包含了GameObject,也包含了BaseSystem,在每一个单位时间内,GameWorld会调用update()方法,此方法会调用所有BaseSystem的**update()**方法。

以上就是一些基本概念,下面来进一步完善内容:

(未完待续)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值