IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 2021SC@SDUSC山东大学软件学院软件工程应用与实践--Ebiten代码分析 源码分析(二) -> 正文阅读

[游戏开发]2021SC@SDUSC山东大学软件学院软件工程应用与实践--Ebiten代码分析 源码分析(二)

2021SC@SDUSC

一.概述

本文将以ebiten源码中自带的游戏2048为例子(位于examples/2048文件夹内),逐步介绍在游戏开发过程中所使用的相关函数工具。
在这里插入图片描述
首先考虑游戏实现过程中所需要的功能,例如:
界面窗口大小,界面窗口位置,窗口标题,游戏的初始化,游戏运行过程中的更新界面等。

下面将依次介绍实现这些功能的具体代码。

二.代码分析

func main() {
	game, err := twenty48.NewGame() //先调用game下的新建游戏
	if err != nil {
		log.Fatal(err) //异常处理
	}
	ebiten.SetWindowSize(twenty48.ScreenWidth, twenty48.ScreenHeight)
	ebiten.SetWindowTitle("2048 (Ebiten Demo)")  //设置基本参数
	if err := ebiten.RunGame(game); err != nil { //运行游戏对象
		log.Fatal(err)
	}
}

首先是游戏2048的main.go文件,调用了ebiten包的SetWindowSize()方法,SetWindowTiTle()方法和RunGame()方法。

1.SetWindowSize()方法

此方法位于ebiten源码的根目录下的window.go文件内。

//SetWindowSize设置桌面上的窗口大小。
//SetWindowSize在其他环境下不执行任何操作。
//。
//全屏模式下,SetWindowSize设置原始窗口大小。
//。
//如果宽度或高度不是正数,SetWindowSize会死机。
//。
//SetWindowSize是并发安全的。
func SetWindowSize(width, height int) {
	if width <= 0 || height <= 0 {
		panic("ebiten: width and height must be positive")
	}
	if w := uiDriver().Window(); w != nil {
		w.SetSize(width, height)
	}
}

当传入的宽高参数为负数时,抛出异常:宽高必须为正。
接下来调用uiDriver().Window()来获取当前的窗口对象。

其中uiDriver()方法位于根目录下的 uidriver_glfw.go 文件内:

func uiDriver() driver.UI {
	return glfw.Get()
}

作用是获取用户接口类型的结构体 UserInterface ,其中包含如下属性:

type UserInterface struct {
	context driver.UIContext
	title   string
	window  *glfw.Window

	//windowWidth和windowHeight表示窗口大小。
	//单位为设备相关像素
	windowWidth  int
	windowHeight int

	//单位是与设备无关的像素
	minWindowWidthInDP  int
	minWindowHeightInDP int
	maxWindowWidthInDP  int
	maxWindowHeightInDP int

	running              uint32
	toChangeSize         bool
	origPosX             int
	origPosY             int
	runnableOnUnfocused  bool
	fpsMode              driver.FPSMode
	iconImages           []image.Image
	cursorShape          driver.CursorShape
	windowClosingHandled bool
	windowBeingClosed    bool

	//必须从主线程访问setSizeCallbackEnabled。
	setSizeCallbackEnabled bool

	//err必须从主线程访问。
	err error

	lastDeviceScaleFactor float64

	//初始化后这些值不变。
	//TODO:更改初始窗口位置时是否应该更新全屏大小?
	initMonitor              *glfw.Monitor
	initFullscreenWidthInDP  int
	initFullscreenHeightInDP int

	initTitle               string
	initFPSMode             driver.FPSMode
	initFullscreen          bool
	initCursorMode          driver.CursorMode
	initWindowDecorated     bool
	initWindowResizable     bool
	initWindowPositionXInDP int
	initWindowPositionYInDP int
	initWindowWidthInDP     int
	initWindowHeightInDP    int
	initWindowFloating      bool
	initWindowMaximized     bool
	initScreenTransparent   bool
	initFocused             bool

	fpsModeInited bool

	input   Input
	iwindow window

	sizeCallback              glfw.SizeCallback
	closeCallback             glfw.CloseCallback
	framebufferSizeCallback   glfw.FramebufferSizeCallback
	framebufferSizeCallbackCh chan struct{}

	t thread.Thread
	m sync.RWMutex
}

上述属性通过如下方式进行初始化:

var (
	theUI = &UserInterface{
		runnableOnUnfocused:     true,
		minWindowWidthInDP:      glfw.DontCare,
		minWindowHeightInDP:     glfw.DontCare,
		maxWindowWidthInDP:      glfw.DontCare,
		maxWindowHeightInDP:     glfw.DontCare,
		origPosX:                invalidPos,
		origPosY:                invalidPos,
		initFPSMode:             driver.FPSModeVsyncOn,
		initCursorMode:          driver.CursorModeVisible,
		initWindowDecorated:     true,
		initWindowPositionXInDP: invalidPos,
		initWindowPositionYInDP: invalidPos,
		initWindowWidthInDP:     640,
		initWindowHeightInDP:    480,
		initFocused:             true,
		fpsMode:                 driver.FPSModeVsyncOn,
	}
)

而之前uiDriver()方法中的glfw.Get()方法就是获取上述的 theUI 对象,方法定义在 internal/glfw/ui.go 文件内:

func Get() *UserInterface {
	return theUI
}

且此文件内同样定义了uiDriver().Window()的Window()方法:

func (u *UserInterface) Window() driver.Window {
	return &u.iwindow
}

返回上述theUI对象中的iwindow属性。

至此,已经成功获取到了当前游戏的窗口对象,下面是设置窗口大小 SetSize() 方法,定义在 internal/glfw/glfw_windows.go 文件内:

func (w *Window) SetSize(width, height int) {
	glfwDLL.call("glfwSetWindowSize", w.w, uintptr(width), uintptr(height))
	panicError()
}

其中call函数是定义在 internal/glfw/load_windows.go 文件内的:

func (d *dll) call(name string, args ...uintptr) uintptr {
	if d.procs == nil {
		d.procs = map[string]*windows.LazyProc{} //为空就初始化一个值
	}
	if _, ok := d.procs[name]; !ok {
		d.procs[name] = d.d.NewProc(name) //相对于当name标识的指令是ok的,那么就调用NewProc方法
	}
	//似乎没有办法正确处理Windows错误。
	r, _, _ := d.procs[name].Call(args...) //返回三个值,两个uintptr,一个error
	return r
}

相当于传入的第一个参数是指令的名称,根据这个名称即 “glfwSetWindowSize” 再执行下一级Call函数:

func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	p.mustFind()
	return p.proc.Call(a...)
}

该方法是go语言自带的方法,在此就不过多赘述了,此方法的参数列表对应着之前的window对象以及需要改变的width和height,此时的Call方法定义如下:

func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	switch len(a) {
	case 0:
		return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
	case 1:
		return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
	case 2:
		return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
	case 3:
		return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
	case 4:
		return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
	case 5:
		return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
	case 6:
		return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
	case 7:
		return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
	case 8:
		return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
	case 9:
		return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
	case 10:
		return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
	case 11:
		return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
	case 12:
		return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
	case 13:
		return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
	case 14:
		return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
	case 15:
		return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
	default:
		panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
	}
}

可见此时已经是在执行系统调用了。

总结,ebiten的修改窗口大小的方法步骤大致如下:输入目的窗口宽高,获取当前窗口对象,利用glfw的方法多次传参,通过系统调用最终修改游戏窗口的大小。

2.SetWindowTiTle()方法

同理,与设置窗口大小方法相似,不同之处在于设置标题方法需要将字符串类型的title参数转化成byte类型的数组:

func (w *Window) SetTitle(title string) {
	s := []byte(title)
	s = append(s, 0)
	defer runtime.KeepAlive(s)
	glfwDLL.call("glfwSetWindowTitle", w.w, uintptr(unsafe.Pointer(&s[0])))
	panicError()
}

然后依旧是传递参数,通过系统调用设置游戏窗口的标题。

3.RunGame()方法

RunGame()方法的作用是启动主循环并运行游戏。是每个游戏最重要的功能,需要一个Game类型的参数,位于根目录下的 run.go 文件中,其中Game类型的定义如下:

type Game interface {
	Update() error
	Draw(screen *Image)
	Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int)
}

Game定义了游戏的必备功能。
其中Update()函数每帧执行一次用来更新游戏的逻辑,Draw()函数同样是每帧执行一次用来更新游戏的画面,Layout()方法是游戏界面的布局方式,几乎每一帧都会调用。

RunGame()定义如下:

func RunGame(game Game) error {
	defer atomic.StoreInt32(&isRunGameEnded_, 1) //将1的值给到&isRunGameEnded_,并且延迟到最后执行,表示游戏已经结束

	initializeWindowPositionIfNeeded(WindowSize()) //初始化窗口位置
	theUIContext.set(&imageDumperGame{
		game: game,
	}) //初始化ui背景
	if err := uiDriver().Run(theUIContext); err != nil { //调用ui的rungame方法
		if err == driver.RegularTermination {
			return nil
		}
		return err
	}
	return nil
}

关键方法是 uiDriver().Run(theUIContext) 即运行方法,位于 internal/uidriver/glfw/run_notsinglethread.go 文件内,定义如下:

func (u *UserInterface) Run(uicontext driver.UIContext) error {
	u.context = uicontext

	//首先初始化主线程,以便该线程在u.run时可用(#809)。
	u.t = thread.NewOSThread()
	graphicscommand.SetMainThread(u.t)

	ch := make(chan error, 1)
	go func() {
		defer func() {
			_ = u.t.Call(func() error {
				return thread.BreakLoop
			})
		}()

		defer close(ch)

		if err := u.t.Call(func() error {
			return u.init()
		}); err != nil {
			ch <- err
			return
		}

		if err := u.loop(); err != nil {
			ch <- err
			return
		}
	}()

	u.setRunning(true)
	u.t.Loop()
	u.setRunning(false)
	return <-ch
}

其中关键是最后三行函数调用,首先是setRunning(true)设置游戏的状态是运行中,然后启动Loop()线程循环,可以开始重复调用Update,Draw,Layout函数了。

其中函数定义如下:
位于internal/uidriver/glfw/ui.go文件内

func (u *UserInterface) setRunning(running bool) {
	if running {
		atomic.StoreUint32(&u.running, 1)
	} else {
		atomic.StoreUint32(&u.running, 0)
	}
}

位于internal/thread/thread.go文件内

//循环开始线程循环,直到POST函数返回BreakLoop。
//。
//必须在线程上调用Loop。
func (t *OSThread) Loop() {
	for f := range t.funcs {
		err := f()
		if err == BreakLoop {
			t.results <- nil
			return
		}
		t.results <- err
	}
}

总结,RunGame()方法是通过开启一个主循环线程反复调用game对象的Update方法,Draw方法和Layout方法,来保证游戏的正常运行的。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-10-11 17:50:06  更:2021-10-11 17:51:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 2:48:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码