阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》

这篇具有很好参考价值的文章主要介绍了阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

MaxScript学习笔记目录

大家好,我是阿赵
之前写了一些MaxScript的学习笔记,里面实现的功能不算很复杂,所以都是使用了偏向于面向过程的方式去编写的。
我本人其实是比较习惯用面向对象的方式去编写代码。关于面向过程和面向对象之间的优缺点对比,各位如果不是很熟悉的话,有空可以去自行查询了解一下。按我自己的理解简单概括一下:
1、面向过程运行的效率高,但如果代码逻辑复杂的时候,修改和维护的难度会比较大
2、面向对象性能较差,内存也占用得比较多,但它易于管理和维护,扩展性好
在编写MaxScript的时候,可以用Struct结构体来部分实现面向对象,下面来介绍一下。

一、Struct的基础使用

1、例子:

在说理论之前,先看一个简单的例子:

(
local TestPerson
local PrintPerson
struct person (name,age,sex,height = 175)

fn TestPerson = 
(
	
	local Tom = person()
	Tom.name = "Tom"
	Tom.age = 23
	Tom.sex = "male"
	Tom.height = 180	
	PrintPerson Tom
	
	local Bill = person name:"Bill" age:19 sex:"male"
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = "";
	content +=("name:"+ obj.name+"\n")
	content +=("age:"+ (obj.age as string) + "\n")
	content += ("sex:"+obj.sex+ "\n")
	content += ("height:"+(obj.height as string)+"\n")
	print content
	return "ok"
)

TestPerson()

)

运行代码,可以看到打印输出:

"name:Tom
age:23
sex:male
height:180
"
"name:Bill
age:19
sex:male
height:175
"
"ok"

2、Struct的创建和属性使用

从上面的例子可以看出,我定义了一个叫做person的结构体。这个person结构体里面有以下几个属性:name、age、sex、height,其中height是有默认值175的
从单词的意思上就可以知道,这是一个人物信息的结构体,它包含了名字、年龄、性别、身高这4个属性。
接下来使用person结构体来创建对象。
从上面的例子里面可以看出,有2种方式可以创建对象

1.创建一个空的person

local Tom = person()
	Tom.name = "Tom"
	Tom.age = 23
	Tom.sex = "male"
	Tom.height = 180	

用struct名称加括号,可以创建出这个struct的对象。
然后可以在对象后面接”.属性名称”,来给对象身上的属性赋值。

2.创建的时候指定参数

local Bill = person name:"Bill" age:19 sex:"male"

在创建的时候,也可以直接用属性的”名称:值”的方式直接赋值给属性。

3、Struct内部定义方法

除了简单的指定一些变量属性,Struct还可以写函数在里面,然后从外部调用函数。
把上面的例子稍作修改,变成这样:

(
local TestPerson
local PrintPerson
struct person (
	name,
	age,
	sex,
	height = 175,
	fn GetPersonInfoString = 
	(
		local content = "";
		content +=("name:"+ this.name+"\n")
		content +=("age:"+ (this.age as string) + "\n")
		content += ("sex:"+this.sex+ "\n")
		content += ("height:"+(this.height as string)+"\n")
	)
)

fn TestPerson = 
(
	
	local Tom = person()
	Tom.name = "Tom"
	Tom.age = 23
	Tom.sex = "male"
	Tom.height = 180	
	PrintPerson Tom
	
	local Bill = person name:"Bill" age:19 sex:"male"
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()

)

运行脚本,会发现结果和之前是一样的。
这里我在person这个结构体里面定义了一个叫做GetPersonInfoString的函数,然后把自身的信息往外返回,在打印信息的时候,只需要向person对象调用GetPersonInfoString函数,就得到了需要打印的结果了。

4、注意事项

1.struct内部的变量和函数的分隔

从上面的例子可以看出,struct的变量和函数,都必须用逗号分割。特别是函数,很容易漏掉。不过在最后一个变量或者函数后,就不能再加逗号了。

2.struct的变量和方法命名

(1)struct内部调用外部变量

来看一段例子:

(
local TestPerson
local PrintPerson
local testVal = 1
struct person (
	name,
	age,
	sex,
	height = 175,
	fn GetPersonInfoString = 
	(
		testVal = testVal +1
		local content = testVal as string
		return content
	)
)

fn TestPerson = 
(	
	local Tom = person()
	Tom.name = "Tom"
	Tom.age = 23
	Tom.sex = "male"
	Tom.height = 180	
	PrintPerson Tom
	
	local Bill = person name:"Bill" age:19 sex:"male"
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()

)

这段例子是从上面的例子改的,需要注意的地方是,我在struct外部定义了一个局部变量testVal = 1,然后在struct内部使用这个testVal并对其进行赋值。
运行脚本,会发现打印如下:

"2"
"3"
"ok"

可以看出,在struct内部是可以调用外部的变量的。

(2)struct内部变量和外部变量重名

再对这个例子进行小修改:

(
local TestPerson
local PrintPerson
local height = 1
struct person (
	name,
	age,
	sex,
	height = 175,
	fn GetPersonInfoString = 
	(
		height = height +1
		local content = height as string
		return content
	)
)

fn TestPerson = 
(	
	local Tom = person()
	Tom.name = "Tom"
	Tom.age = 23
	Tom.sex = "male"
	Tom.height = 180	
	PrintPerson Tom
	
	local Bill = person name:"Bill" age:19 sex:"male"
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()
print ("height:"+(height as string))
)

这次我把testVal改名了,改成和Struct里面的height变量重名了。
运行脚本,可以看到:

"181"
"176"
"height:1"

从这里我们可以看出来,如果Struct的变量和外部重名了,在使用的时候,会使用内部的变量。

3.内部参数问题

再对例子做小修改:

(
local TestPerson
local PrintPerson
local height = 1
struct person (
	name,
	age,
	sex,
	height = 175,
	fn GetPersonInfoString = 
	(
		height = height +1
		local content = height as string
		return content
	),	
	fn SetHeight height = 
	(
		height = height
	)
)

fn TestPerson = 
(	
	
	local Bill = person name:"Bill" age:19 sex:"male"
	Bill.SetHeight 200
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()
print ("height:"+(height as string))
)

这次,我加了一个SetHeight 函数在struct里面,传进去一个height的变量,然后很莫名其妙的height = height,因为height这个名字出现在了3个地方,首先是struct外部的变量,然后是struct本身的变量,最后是函数的传入变量,那么这个height究竟是代表了哪个?
看看打印结果

"176"
"height:1"

可以看出,struct内部的height变量并没有收到height = height的影响,struct外部的height变量也没有受到height = height的影响,这里赋值的只是函数传进去的height参数。
再进行一下修改:

(
local TestPerson
local PrintPerson
local height = 1
struct person (
	name,
	age,
	sex,
	height = 175,
	fn GetPersonInfoString = 
	(
		height = height +1
		local content = height as string
		return content
	),	
	fn SetHeight height = 
	(
		this.height = height
	)
)

fn TestPerson = 
(	
	
	local Bill = person name:"Bill" age:19 sex:"male"
	Bill.SetHeight 200
	PrintPerson Bill
)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()
print ("height:"+(height as string))
)

这次把SetHeight 函数的内容改成了

this.height = height

这次的输出结果是:

"201"
"height:1"

可以看出,struct的height被赋值了。

4.公共和私有变量函数

对上面的例子再做修改:

(
local TestPerson
local PrintPerson
struct person (
	name,
	age,
	sex,
	height = 175,
	
	fn GetPersonInfoString = 
	(
		return (this.GetWeightStr())
	),	

	fn SetWeight val = 
	(
		this.weight = val
	),
	private weight = 1,
	private fn GetWeightStr = 
	(
		local content = this.weight as string
		return content
	)
)

fn TestPerson = 
(	
	
	local Bill = person name:"Bill" age:19 sex:"male"
	Bill.SetWeight 100
	
	PrintPerson Bill

)

fn PrintPerson obj = 
(
	local content = obj.GetPersonInfoString()
	print content
	return "ok"
)

TestPerson()
)

可以看到在struct里面,多了一个私有的变量weight,还有一个私有的函数GetWeightStr。
这样写是允许的,这个weight变量和GetWeightStr方法,是只有struct内部才能使用,如果在外部调用,就会报错。
还有一点值得注意的是,在struct里面,可以用一个public或者private定义一列连续的变量或者函数
于是我们可以把struct内部的代码改成这样:

struct person (
	public
	name,
	age,
	sex,
	height = 175,
	
	fn GetPersonInfoString = 
	(
		return (this.GetWeightStr())
	),	
	fn SetWeight val = 
	(
		this.weight = val
	),
	private 
	weight = 1,
	fn GetWeightStr = 
	(
		local content = this.weight as string
		return content
	)
)

在public下面的一段变量和函数,都是公共的,在private下面的一段变量和函数,都是私有的。
public和private没有严格的顺序和数量,可以先private后public也行,或者先private再public再private都可以。

5.总结

说了这么多废话,对于本身熟悉其他语言编程的朋友来说,肯定觉得很无聊。其实我是为了照顾本身对编程不是特别熟悉的朋友,上面的实验得出了一个结论,struct里面的变量定义,和一般的脚本语言没区别,作用域是优先当前结构本身,然后才是往上一级。然后如果要指定struct本身,可以使用this来指定。
然后关于public和private,也是和一般脚本区别不大,它可以通过一个public或者private标记一系列连着的变量和函数,

二、关于面向对象的思考

通过上面的例子,我们似乎看到了面向对象的一丝希望,对于复杂的逻辑,我们可以通过Struct结构定义类似于类(Class)的结构,然后把数据都封装在结构体的对象里面,然后定义方法,把处理的逻辑都写在结构体里面。最后,在外部调用时,只需要构建对象,并且传入数据,剩下的逻辑都在对象内部处理。
不过Struct不是Class,它的功能比较有限。比如如果按照严格的概念定义,面向对象应该包含封装、继承、多态。
从上面的例子看,Struct实现封装是没问题的,以为他有public和private的定义。
继承和多态,struct并没有直接现成的方法可以做到。如果非要说能实现继承,也可以通过定义一个方法,在创建子类的时候,把父类传进去,然后复制所有父类变量和方法的定义,最后在子类实现重写,覆盖父类的方法,也能勉强能实现。但我个人感觉,这样子做,变成了是为了实现面向对象而实现,好像有点跑偏了。
从我个人的理解,面向对象的目的是为了让代码变得条例清晰,便于管理。对象内部的问题对象自己解决,外部只负责调用和得到结果。基于这个理念,我觉得不一定非要实现继承和多态,只要在编写代码的时候能比较清晰的划分业务范围,就可以使用面向对象的方式去写maxscript的脚本了。

三、相对完整的应用例子

这里写了一个获取一个biped骨骼所有骨骼的信息的脚本,通过这个脚本,可以看看较为具体的写法。

1、完整代码:

(
	--function
	local CheckOneObj
	local PrintBoneInfo
	local OnPickFun
	local AddBoneInfoToDict
	local GetBoneInfoByName
	--var
	local TestPickUI
	local boneNameList;
	local boneDict
	struct TransformInfo
	(
		pos,
		rotation,
		scale,
		fn SetData obj =
		(
			this.pos = obj.transform.pos
			this.rotation = obj.transform.rotation
			this.scale = obj.transform.scale
		),		
		fn GetPrintString = 
		(
			local content = ""
			content += "pos:"+(this.pos as string)+"\n"
			content += "rotation:"+(this.rotation as string+"\n")
			content +="scale:"+(this.scale as string+"\n")
			return content
		)
	)
	

	
	struct BoneInfo
	(
		public
		name,
		transform,
		children,
		parent,
		fn SetData obj = 
		(
			this.name = obj.name
			this.transform = TransformInfo()
			this.transform.SetData obj
			this.SetParent obj
			this.SetChildren obj
			
		),
		fn GetInfoString = 
		(
			local content = this.GetNameString() + this.GetTransformString()+this.GetParentString()+this.GetChildrenString()
			return content
			
		),
		
		private 		
		fn SetParent obj = 
		(
			if obj.parent == undefined then
			(
				this.parent = undefined
			)
			else
			(
				this.parent = obj.parent.name
			)
		),
		fn SetChildren obj = 
		(
			local childrenList = obj.children
			if childrenList != undefined and childrenList.count >0 then
			(
				this.children = #()
				for i in 1 to childrenList.count do 
				(
					append this.children childrenList[i].name
				)
			)
			else
			(
				this.children = undefined
			)
		),		
		fn GetNameString = 
		(
			local content = "----------\n";
			if this.name == undefined then
			(
				content += "name:null\n"
			)
			else
			(
				content += "name:"+this.name+"\n";
			)
			return content
		),
		fn GetTransformString = 
		(
			local content = "";
			if this.transform != undefined then
			(
				content = "transform:\n";
				content += this.transform.GetPrintString()
			)
			else
			(
				content = "transform:null\n"
				
			)
			return content;
		),		
		
		fn GetParentString = 
		(
			local content = "";
			if this.parent == undefined then
			(
				content = "parent:null\n"
			)
			else
			(
				content = "parent:"+this.parent+"\n";
			)
			
		),
		
		
		fn GetChildrenString = 
		(
			local content = "";
			if this.children == undefined or this.children.count == 0 then
			(
				content = "children:null\n"
			)
			else
			(
				content = "children:"
				for i in 1 to this.children.count do
				(
					content += this.children[i]
					if i<this.children.count then
						content +=","
				)
				content +="\n"
				
				
			)
			return content
		)
		
	)
	
	fn AddBoneInfoToDict info = 
	(
		if boneNameList == undefined then
			boneNameList = #()
		if boneDict == undefined then
			boneDict = #()
		local index = findItem boneNameList info.name
		if index <=0 then
		(
			append boneNameList info.name
			append boneDict info
		)
		else
		(
			boneDict[index] = info
		)
	)
	
	fn GetBoneInfoByName val = 
	(
		if boneNameList == undefined or boneNameList.count == 0 then 
			return undefined
		
		local index = findItem boneNameList val
		if index <=0 then
		(
			return undefined
		)
		else
		(
			return boneDict[index]
		)
	)
	
	fn CheckOneObj obj = 
	(
		local info = BoneInfo()
		info.SetData obj
		AddBoneInfoToDict info
		local childrenList  = obj.children
		if childrenList != undefined and childrenList.count >0 then
		(
			for i in 1 to childrenList.count do
			(
				CheckOneObj childrenList[i];
			)
		)
	)
	
	fn PrintBoneInfo = 
	(
		if boneDict != undefined and boneDict.count >0 then
		(
			for i in 1 to boneDict.count do
			(
				print(boneDict[i].GetInfoString())
			)
		)
	)
	
	fn OnPickFun = 
	(
		if $ == undefined then
			return 0
		boneNameList = #()
		boneDict = #()
		CheckOneObj $
		PrintBoneInfo()
		
	)
	
	
	rollout TestPickUI "Untitled" width:199 height:177
	(
		button 'btn1' "pick" pos:[51,48] width:110 height:31 align:#left
		on btn1 pressed do
		(
			OnPickFun()
		)
	)
	createDialog TestPickUI
		
)

2、执行脚本的结果
运行脚本,会看到只有一个按钮:
阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》

创建一个biped骨骼
阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》

选择biped的根节点,然后点击pick按钮
阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》

会发现输出了很多打印。以横线分割,每一段是一根骨骼的信息。

3、代码说明:

1.struct的说明

这个脚本里面定义了2个结构体,分别是TransformInfo和BoneInfo。其中TransformInfo作为BoneInfo里面的一个变量,记录了骨骼的Transform信息。
BoneInfo除了Transform信息,还有名字、子节点、父节点的信息。
在使用方面,都是直接新建对应的对象,然后用SetData函数把物体对象传进去,然后在对象内部进行的数据分析和记录。
在最后,调用了BoneInfo的GetInfoString函数,获取物体的各种参数。而每一种属性的参数,都是有独立的方法去组建打印的字符串。如果想修改其中一种信息,可以只修改对应的方法。

2.数据存储

脚本里面有写函数,在这个例子里面是没有用到的,我是想顺便展示一下怎样去做这个事情。

fn AddBoneInfoToDict info = 
	(
		if boneNameList == undefined then
			boneNameList = #()
		if boneDict == undefined then
			boneDict = #()
		local index = findItem boneNameList info.name
		if index <=0 then
		(
			append boneNameList info.name
			append boneDict info
		)
		else
		(
			boneDict[index] = info
		)
	)
	
	fn GetBoneInfoByName val = 
	(
		if boneNameList == undefined or boneNameList.count == 0 then 
			return undefined
		
		local index = findItem boneNameList val
		if index <=0 then
		(
			return undefined
		)
		else
		(
			return boneDict[index]
		)
	)

本来事情很简单,如果有dictionary或者哈希表这类的数据类型,直接用key和value来存储是很简单的事情。但MaxScript并没有这样的类型,所以我就用其他方法实现了。
这里是建了了2个数组,一个是存储骨骼的名字boneNameList,一个是存储名字对应的对象boneDict,保证两个数组的下标是一样的,然后通过函数AddBoneInfoToDict添加数据,通过函数GetBoneInfoByName来获取数据,获取的时候,先通过名字判断是否在boneNameList数组里存在,如果存在,返回了下标,就通过下标去boneDict数组拿对象。文章来源地址https://www.toymoban.com/news/detail-419886.html

到了这里,关于阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go 笔记 第九章 结构体 struct 声明和使用

    package main import “fmt” type qm struct { name string age int hobby []string home } type home struct { address string } // 给结构体声明方法 func (q qm) song(name string) (ret string) { ret = “惊雷” fmt.Printf(“%v—%v–%v”, q.name, name, q.age) fmt.Println() return ret } func (h home) open() { fmt.Println(“open”) } func main() { v

    2024年02月16日
    浏览(42)
  • web学习笔记(四十四)

    目录 1. 接口 1.1 什么是接口 1.2 接口的请求过程 1.3 接口文档 1.3.1 什么是接口文档 1.3.2 接口文档的组成部分 1.3.3 接口文档示例  2. XMLHttpRequest 2.1 什么是XMLHttpRequest 2.2 XHR发起Get请求的步骤 2.3 XHR发起post请求的步骤 2.4 什么是查询字符串   2.5 什么是XML        使用Ajax 请求数

    2024年04月17日
    浏览(35)
  • JUC并发编程学习笔记(十四)异步回调

    Future设计的初衷:对将来的某个事件的结果进行建模 在Future类的子类中可以找到CompletableFuture,在介绍中可以看到这是为非异步的请求使用一些异步的方法来处理 点进具体实现类中,查看方法,可以看到CompletableFuture中的异步内部类,里面是实现的异步方法 以及一些异步方法

    2024年02月05日
    浏览(45)
  • C++学习笔记(三十四):c++ array

    本节介绍c++标准库模板中的array和c风格的array的区别,及什么时候使用std::array。

    2024年01月23日
    浏览(46)
  • 【视觉SLAM十四讲学习笔记】第五讲——相机模型

    专栏系列文章如下: 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元数 【视

    2024年01月17日
    浏览(39)
  • EMC学习笔记(十四)射频PCB的EMC设计(一)

    近十年来,移动通信飞速发展,在移动通信设备的设计、测试、安装和操作维护中,必须仔细考虑系统间、设备间、设备内部的器件间的EMC问题。EMC的控制技术中,屏蔽、滤波、接地是三项最基本的干扰抑制技术,主要是用来切断干扰的传输途径。 由于射频电路的特殊性,其

    2024年02月12日
    浏览(36)
  • 密码学学习笔记(二十四):TCP/IP协议栈

    TCP/IP协议栈的基础结构包括应用层、传输层、网络层、数据链路层和物理层。 应用层位于TCP/IP协议栈的最顶层,是用户与网络通信的接口。这一层包括了各种高级应用协议,如HTTP(用于网页浏览)、FTP(用于文件传输)、SMTP(用于电子邮件)和DNS(用于域名解析)。应用层

    2024年01月19日
    浏览(52)
  • Python学习笔记第六十四天(Matplotlib 网格线)

    我们可以使用 pyplot 中的 grid() 方法来设置图表中的网格线。 grid() 方法语法格式如下: 参数说明: b:可选,默认为 None,可以设置布尔值,true 为显示网格线,false 为不显示,如果设置 **kwargs 参数,则值为 true。 which:可选,可选值有 ‘major’、‘minor’ 和 ‘both’,默认为

    2024年02月12日
    浏览(43)
  • 【AI】《动手学-深度学习-PyTorch版》笔记(十四):多层感知机

    在前面介绍过,使用softmax回归来处理分类问题时,每个输出通过都一个仿射函数计算,网络结构如下,输入和输出之间为全链接层: 多层感知机就是在输入和输出中间再添加一个或多个全链接层,将中间的层称为“隐藏层”,下图为添加了一个全链接层的网络结构: 现实世

    2024年02月13日
    浏览(43)
  • UE5学习笔记(十四)——蓝图基础之第一次做界面

    目录 制作一个简单的UI 步骤1:添加一个界面,并显示在屏幕上 【知识点】在关卡界面调用控件的值 步骤2:蓝图控制文字改变

    2024年02月04日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包