Love2d从青铜到王者第十篇:Love2d之类和类的继承(Classes And Inheritance)

Posted 森明帮大于黑虎帮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Love2d从青铜到王者第十篇:Love2d之类和类的继承(Classes And Inheritance)相关的知识,希望对你有一定的参考价值。

系列文章目录


文章目录


前言


🍇一、类(Classes)

1️⃣.类(Classes)

  • Classes 就像蓝图。您可以用一个Classes 创建多个房屋。类似地,我们可以从一个类中创建多个对象。

  • 对于Classes ,我们将使用一个库:Classic

  • 点击classic.lua然后按行的复制代码。

  • 转到文本编辑器,创建一个名为classic.lua然后粘贴代码。
  • 现在我们必须require它。
function love.load()
    Object=require "classic"
end

function love.update(dt)
end

function love.draw()
end
  • 现在我们已经准备创建一个类。创建一个名为rectangle.lua,并放入以下代码:
--! file: rectangle.lua

-- Pass Object as first argument.
Rectangle = Object.extend(Object)

function Rectangle.new(self)
    self.test = math.random(1, 1000)
end
  • 一切都会得到解释。但是首先将这段代码放入main.lua
--! file: main.lua
function love.load()
    Object = require "classic"
    --Don't forget to load the file
    require "rectangle"

    r1 = Rectangle()
    r2 = Rectangle()
    print(r1.test, r2.test)
end

function love.update(dt)
end

function love.draw()
end

  • 当您运行游戏时,您将看到打印了2个随机数。
  • 所以让我们一步一步地看这段代码。首先,我们用Rectangle = Object.extend(对象)。这使得Rectangle成为一个Classes 。这将是我们的蓝图。与属性相反,Classes 通常用大写字符编写(因此这将是uppercase或PascalCase)
  • main.lua。我们说r1 = Rectangle()。即使Rectangle是一个表,我们仍然可以像调用函数一样调用它。它如何工作的是另一个章节。但是通过调用Rectangle()它创建一个新的实例。这意味着它采用我们的蓝图,创建一个具有所有类特性的新对象。每个新实例都是独一无二的。
  • 为了证明这一点r1是唯一的,我们创建另一个名为r2。双方都有属性值 test,但他们的变量不同。
  • 当我们调用Rectangle(),它执行调用Rectangle.new。这就是我们所说的构造器。
  • 参数self,是我们正在修改的实例。如果我们打字Rectangle.test = math.random(0, 1000),我们将把属性赋予蓝图,而不是用蓝图创建的实例。
  • 让我们对我们的类进行一些更改:
--! file: rectangle.lua
Rectangle = Object.extend(Object)

function Rectangle.new(self)
    self.x = 100
    self.y = 100
    self.width = 200
    self.height = 150
    self.speed = 100
end
function Rectangle.update(self, dt)
    self.x = self.x + self.speed * dt
end

function Rectangle.draw(self)
    love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end
  • 这就是移动矩形的对象在Object 组成。除了这次我们将运动和绘图部分的代码放在对象中。现在我们只需要调用updatedraw进去main.lua
--! file: main.lua
function love.load()
    Object = require "classic"
    --Don't forget to load the file
    require "rectangle"

    r1 = Rectangle()
    r2 = Rectangle()
    print(r1.test, r2.test)
end

function love.update(dt)
    r1.update(r1,dt)
end

function love.draw()
    --love.graphics.print(r1.test,100,100)
    --love.graphics.print(r2.test,100,200)
    r1.draw(r1,100,200)
end

  • 当你运行游戏时,你会看到一个移动的矩形。
  • 所以我们创建了一个名为Rectangle()。我们创建了一个名为r1。所以现在r1具有功能updatedraw。我们调用这些函数,作为第一个参数,我们传递实例本身r1。这是什么self变成了函数。
  • 不过,我们不得不通过,这有点烦人r1每次我们调用它的一个函数。幸运的是,Lua对此有一个简写。当我们使用冒号(:)时,函数调用将自动传递冒号左边的对象作为第一个参数。
--! file: main.lua
function love.load()
    Object = require "classic"
    --Don't forget to load the file
    require "rectangle"

    r1 = Rectangle()
    r2 = Rectangle()
    print(r1.test, r2.test)
end

--[[
function love.update(dt)
    r1.update(r1,dt)
end
--]]
function love.update(dt)
    r1:update(dt)
end

function love.draw()
    --love.graphics.print(r1.test,100,100)
    --love.graphics.print(r2.test,100,200)
    --r1.draw(r1,100,200)
    r1:draw()
end

  • 我们也可以用函数来做这件事。
--! file: rectangle.lua

--Lua turns this into: Object.extend(Object)
Rectangle = Object:extend()

--Lua turns this into: Rectangle.new(self)
function Rectangle:new()
    self.x = 100
    self.y = 100
    self.width = 200
    self.height = 150
    self.speed = 100
end

--Lua turns this into: Rectangle.update(self, dt)
function Rectangle:update(dt)
    self.x = self.x + self.speed * dt
end

--Lua turns this into: Rectangle.draw(self)
function Rectangle:draw()
    love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end

  • 我们称之为语法糖。 Syntactic sugar
  • 让我们添加一些参数到Rectangle:new()
--Lua turns this into: Object.extend(Object)
Rectangle = Object:extend()

--! file: rectangle.lua
function Rectangle:new(x, y, width, height)
    self.x = x
    self.y = y
    self.width = width
    self.height = height
    self.speed = 100
end

--Lua turns this into: Rectangle.update(self, dt)
function Rectangle:update(dt)
    self.x = self.x + self.speed * dt
end

--Lua turns this into: Rectangle.draw(self)
function Rectangle:draw()
    love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end
  • 有了这个我们可以给r1r2每个都有自己的位置和大小。
--! file: main.lua

function love.load()
    Object = require "classic"
    require "rectangle"
    r1 = Rectangle(100, 100, 200, 50)
    r2 = Rectangle(350, 80, 25, 140)
end

function love.update(dt)
    r1:update(dt)
    r2:update(dt)
end

function love.draw()
    r1:draw()
    r2:draw()
end
  • 所以现在我们有两个移动的矩形。这就是classes如此厉害的原因。r1r2都是一样的,但却是独一无二的。

  • 另一个让classes如此厉害的原因是继承(inheritance)

🍈二、继承(inheritance)

1️⃣.继承(inheritance)

  • 通过继承,我们可以扩展我们的类。换句话说,我们制作了蓝图的副本,并向其中添加了功能,而没有编辑原始蓝图。
  • 假设你有一个怪物游戏。每个怪物都有自己的攻击方式,它们的移动方式不同。但是他们也会受到伤害,甚至死亡。这些重叠的特性应该放在我们称之为超类或者基础类。他们提供了所有怪物都有的特征。然后每个怪物的类可以扩展这个基类,并在其中添加自己的特性。
  • 让我们创建另一个移动的形状,一个圆形。我们的移动矩形和圆形有什么共同之处?他们都会搬走。所以让我们为这两个形状创建一个基类。
  • 创建一个文件名为shape.lua,并放入以下代码:
--! file: shape.lua
Shape = Object:extend()

function Shape:new(x, y)
    self.x = x
    self.y = y
    self.speed = 100
end

function Shape:update(dt)
    self.x = self.x + self.speed * dt
end
  • 我们的基类shape现在处理运动。我应该指出基类只是一个术语。“X是Y的基类”。基类仍然和其他类一样。只是我们使用它的方式不同。

  • 无论如何,现在我们有了一个处理我们运动的基类,我们可以使Rectangle继承shape,并移除其update代码。确保require shape在使用它之前。

--! file: main.lua

function love.load()
    Object = require "classic"
    require "shape"
    require "rectangle"
    r1 = Rectangle()
    r2 = Rectangle()
end
--! file: rectangle.lua
Rectangle = Shape:extend()

function Rectangle:new(x, y, width, height)
    Rectangle.super.new(self, x, y)
    self.width = width
    self.height = height
end

function Rectangle:draw()
    love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end
  • Rectangle = Shape:extend(),我们使Rectangle继承shape
  • Shape有自己的函数,称为:new()。通过创造Rectangle:new()我们覆盖原始函数。意味着当我们调用Rectangle()它不会执行Shape:new()但是相反的执行Rectangle:new()
  • 但是矩形具有这样的属性super子类,这是一个类Rectangle()类是从Shape继承过来的。用Rectangle.super我们可以访问基类的函数,并用它来调用Shape:new()
  • 我们必须通过self作为第一个参数,不能让Lua用冒号(:)来处理,因为我们没有把函数作为实例来调用。
  • 现在我们需要制作一个circle类。创建一个名为circle.lua,并放入下面的代码。
--! file: circle.lua
Circle = Shape:extend()

function Circle:new(x, y, radius)
    Circle.super.new(self, x, y)
    --A circle doesn't have a width or height. It has a radius.
    self.radius = radius
end

function Circle:draw()
    love.graphics.circle("line", self.x, self.y, self.radius)
end
  • 所以我们使Circle继承Shape。我们给了xynew()函数对于ShapeCircle.super.new(self,x,y)
  • 我们给我们的Circle类自己的draw函数。这是你画圆的方法。圆没有宽度和高度,它们有半径。
  • 现在在main.lualoadShape.luaCircle.lua,并改变r2对于Circle
--! file: main.lua

function love.load()
    Object = require "classic"
    --Don't forget to load the file
    require "shape"

    require "rectangle"

    --Don't forget to load the file
    require "circle"

    r1 = Rectangle(100, 100, 200, 50)

    --We make r2 a Circle instead of a Rectangle
    r2 = Circle(350, 80, 40)
end

function love.update(dt)
    r1:update(dt)
    r2:update(dt)
end

function love.draw()
    r1:draw()
    r2:draw()
end

  • 现在当你运行游戏时,你会看到一个移动的矩形和一个移动的圆形。

🍊三、代码详细解读

  • 让我们再看一遍所有的代码。
  • 首先,我们加载library classic require “classic”。加载这个库会返回一个表,我们将这个表存储在里面的对象Object。它有模拟一个类所需要的最基本的东西。因为Lua没有类,但是通过使用classic 的我们得到了一个非常好的类的模仿。
  • 接下来我们加载shape.lua 。在该文件中,我们创建了一个名为Shape 。我们将使用这个类作为基础类为Rectangle Circle 。这些类的两个共同点是它们都有一个x和y变量,并且它水平移动。这些相似之处是我们放进去的变量在Shape
  • 接下来,我们创建Rectanlge 类。我们使Rectanlge 类继承基类。在里面new()函数,这构造函数,我们调用Rectangle.super.new(self,x,y)。我们用self作为第一个参数,所以Shape 将使用我们蓝图的实例,而不是蓝图本身。我们给矩形一个width height 变量,并给它一个绘制函数。
  • 接下来我们重复上面的,除了一个圈。所以不是一个宽度和高度我们给它一个radius 变量。
  • 既然我们已经把classes 准备好了,我们可以开始做了例子这些classes 。随着 r1 = Rectangle(100, 100, 200, 50) 我们创建了类的一个类Rectanlge 。它是一个由我们的蓝图制成的物体,而不是蓝图本身。我们对此实例所做的任何更改都不会影响该类。我们update draw 这个实例,为此我们使用冒号(😃。这是因为我们需要将实例作为第一个参数传递,冒号会让Lua为我们做这件事。
  • 最后,我们做同样的事情r2 ,除了我们让它成为一个Circle

🍋四、疑惑

  • 对于一章来说,这是很多信息,我可以想象如果你很难理解所有这些。如果你是编程新手,你需要一段时间才能理解所有这些新概念,但最终你会习惯的。我会在谈论新主题的同时,不断增加对旧主题的解释。

🍉五、总结

  • Classes 就像蓝图。我们可以从一个类中创建多个对象。为了模拟类,我们使用库classic。您可以用创建一个类ClassName = Object:extend()。您可以使用创建类的实例instanceName = ClassName()。这将调用函数ClassName:new()。这是调用构造函数。类的每个函数都应该以self 开始以便在调用函数时,可以将实例作为第一个参数传递。instanceName.functionName(instanceName)。我们可以使用冒号(:)让Lua替我们做这件事。

  • 我们可以用扩展一个类ExtensionName = ClassName:extend()。这使得ExtensionNameClassName的一份拷贝,以至于我们可以在不改变ClassName的情况下添加变量。如果我们给ExtensionName一种函数ClassName已经有了,我们仍然可以调用原函数用extension name . super . function name(self)

🍋总结

以上就是今天要讲的内容,本文仅仅简单介绍了Love2d之类和类的继承(Classes And Inheritance),介绍了love2d类和类的继承的使用,与博主的lua语言文章结合更好的理解love2d的编码,如果你是一名独立游戏开发者,或者一位对游戏开发有着深厚兴趣,但是又对于unity3d,ue4等这些对于新手而言不太友好的引擎而头疼的开发者;那么现在,你可以试试Love2D。Love2D是一款基于Lua编写的轻量级游戏框架,尽管官方称呼其为引擎,但实际上它只能称得上是一个框架,因为他并没有一套全面完整的解决方案。不过,这款框架上手及其容易,是学习游戏开发的初学者入门的一个良好选择。

以上是关于Love2d从青铜到王者第十篇:Love2d之类和类的继承(Classes And Inheritance)的主要内容,如果未能解决你的问题,请参考以下文章

Love2d从青铜到王者第十六篇:Love2d之动画(Animation)

Love2d从青铜到王者第十一篇:Love2d之图像(Images)

Love2d从青铜到王者第十三篇:Love2d之游戏:射击敌人(Game: Shoot the enemy)

Love2d从青铜到王者第十二篇:Love2d之碰撞检测(Detecting collision)

Love2d从青铜到王者第十四篇:Love2d之分享你的游戏(Distributing your game)

Love2d从青铜到王者第一篇:Love2d入门以及安装教程