golang game development study notes - the development of a simple 2D game (Basics)

This text written in golang game development study notes - to create a 3D world to explore freely after, interested can go to the article to know some basics, in this article we want to create a simple 2D game scene and matching characters, and the characters to achieve movement and collision detection function, the effect is as follows
Here Insert Picture Description

I. References

learnOpenGl the Chinese translation, the use of C++implementation.
go-gl example sample code of go-gl

II. Basic concepts

The concept involved here basically been introduced in the previous article, not repeat them. But we are interested can go look at some collision detection algorithm implementation

III. Dependence

Do not add any dependencies

IV. Resource Preparation

Game world we've created, there are two places need to use a texture resource (map), one is composed of squares of the world, the second is the protagonist of the game. Because the box is static, no animation, so only need a map on it. The protagonist of the game will need to make up more than one texture image animation exercise. Note that, we need to part texture image for only rendering a picture must be with alphathe picture format channel, transparency and the picture is not required to render the position is set to 0. For simplicity, this uniform use pngformat.
The writer is a direct Baidu search 像素图像资源found a piece gifand then screentogifbroken down into still pictures, and finally with open source graphics editing software Kritadeduction directly out (certainly not for commercial use) because there is no static figure drawing, I drew a resource list follows

1. People still picture

Here Insert Picture Description

2. People moving picture (only show the first frame)

Here Insert Picture Description

2. squares texture map

Here Insert Picture Description
将资源准备完成之后,就能开始代码的开发了

五.开始实现!

1.资源管理

在上一篇文章中我们将纹理和着色器分别封装成了两个类,这里我们创建一个资源管理类对这两个类进行管理,由于golang中是没有静态变量的,需要用包内变量对其进行模拟

shader.go
package resource


import(
	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/go-gl/mathgl/mgl32"
	"strings"
	"fmt"
)

type Shader struct{
	ID uint32
}

func Compile(vertexString, fragmentString string) *Shader{
	vertexShader,err := compile(vertexString+"\x00", gl.VERTEX_SHADER)
	if err != nil{
        panic(err)
	}
	fragmentShader,err := compile(fragmentString+"\x00", gl.FRAGMENT_SHADER)
	if err != nil{
        panic(err)
	}
	progID := gl.CreateProgram()
	gl.AttachShader(progID, vertexShader)
	gl.AttachShader(progID, fragmentShader)    
	gl.LinkProgram(progID)
	gl.DeleteShader(vertexShader)
	gl.DeleteShader(fragmentShader)
	return &Shader{ ID: progID}
}
func (shader *Shader) Use(){
	gl.UseProgram(shader.ID)
}

func (shader *Shader) SetBool(name string, value bool){
	var a int32 = 0;
	if(value){
		a = 1
	}
	gl.Uniform1i(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), a)
}

func (shader *Shader) SetInt(name string, value int32){
	gl.Uniform1i(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), value)
}

func (shader *Shader) SetFloat(name string, value float32){
	gl.Uniform1f(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), value)
}

func (shader *Shader) SetMatrix4fv(name string, value *float32){
	gl.UniformMatrix4fv(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), 1,false,value)
}

func (shader *Shader) SetVector3f(name string, vec3 mgl32.Vec3){
	gl.Uniform3f(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), vec3[0], vec3[1], vec3[2]);
}
func compile(sourceString string, shaderType uint32)(uint32, error){
	shader := gl.CreateShader(shaderType)
	source, free := gl.Strs(sourceString)
	gl.ShaderSource(shader, 1, source, nil)
    free()
	gl.CompileShader(shader)

	var status int32
	gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
	if status == gl.FALSE {
        var logLength int32
        gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
        log := strings.Repeat("\x00", int(logLength+1))
        gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
        return 0, fmt.Errorf("failed to compile %v: %v", source, log)
    }
    return shader, nil
}

shader类从给定的两个字符串中编译出顶点着色器和片段着色器,同时提供几个用于设置uniform变量的函数,唯一要说明的是字符串尾部要添加0X00来标识结尾(发现只有golang需要这样)

两个着色器程序

着色器程序的用途大家可以在参考资料或者之前的文章中找到详细说明,这里只从代码上大概讲解一下。需要说明的是,着色器程序需要结合具体要绘制的顶点来看,在这个2D游戏中,所有的元素都是由两个三角形组成的矩形构成的,因此在不使用EBO的情况下需要六个顶点

顶点
vertices := []float32{
		0.0, 1.0, 0.0, 1.0,
        1.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 

        0.0, 1.0, 0.0, 1.0,
        1.0, 1.0, 1.0, 1.0,
        1.0, 0.0, 1.0, 0.0,
	}

由于我们开发的是2D游戏,所有顶点的z分量都为0,所以用顶点中每一行的前两位作为顶点坐标,后两位作为纹理坐标,并将顶点坐标和纹理坐标合为一个变量传入着色器中

顶点着色器
#version 410 core
layout (location = 0) in vec4 vertex; 
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;
uniform int reverseX;

void main()
{   if(reverseX == 1){
        TexCoords = vertex.zw;
    }else{
        TexCoords = vec2(1 - vertex.z, vertex.w);
    }
    gl_Position = projection * view * model * vec4(vertex.x, vertex.y, 0.0, 1.0);
}

着色器程序中modelprojectionview这三个变量用于将物体从局部空间变换到世界空间并转换为用户视角,可以理解为负责对物体进行平移,变形,对画面进行裁剪的工具。在前面的文章中已经讲过,不再赘述。vertex变量就是前面顶点数据中的每一行,其中前两位代表顶点坐标,后两位代表纹理坐标,reverseX变量为unifom类型,同样从外部传入,负责控制图像是否沿Y轴方向进行镜像,具体作用后面会讲到

片段着色器
#version 410 core
in vec2 TexCoords;
out vec4 FragColor;

uniform sampler2D image;
uniform vec3 spriteColor;

void main()
{
    vec4 texColor = texture(image, TexCoords);
    if(texColor.a < 0.1)
        discard;
    FragColor = vec4(spriteColor, 1.0) * texColor;
}

片段着色器与上一篇文章的基本相同,唯一区别是加入了一个判断,在图像区域的透明度小于0.1的时候,会放弃对这片区域的渲染。

texture2D.go
package resource

import(
	"os"
	"image"
	"image/png"
	"image/draw"
	"errors"
	"github.com/go-gl/gl/v4.1-core/gl"
)

type Texture2D struct{
	ID uint32
	TEXTUREINDEX uint32
}
func NewTexture2D(file string, TEXTUREINDEX uint32) *Texture2D{
	imgFile, err := os.Open(file)
	if err != nil {
		panic(err)
	}

	img, err := png.Decode(imgFile)
	if err != nil {
		panic(err)
	}
	rgba := image.NewRGBA(img.Bounds())
	if rgba.Stride != rgba.Rect.Size().X*4 {
		panic(errors.New("unsupported stride"))
	}
	draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
	
	var textureID uint32
	gl.GenTextures(1, &textureID)
	gl.BindTexture(gl.TEXTURE_2D, textureID)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
	gl.TexImage2D(
		gl.TEXTURE_2D,
		0,
		gl.RGBA,
		int32(rgba.Rect.Size().X),
		int32(rgba.Rect.Size().Y),
		0,
		gl.RGBA,
		gl.UNSIGNED_BYTE,
		gl.Ptr(rgba.Pix))
	gl.BindTexture(gl.TEXTURE_2D, 0);
	return &Texture2D{ ID: textureID,TEXTUREINDEX:TEXTUREINDEX}
}

func (texture *Texture2D) Use(){
	gl.ActiveTexture(texture.TEXTUREINDEX)
	gl.BindTexture(gl.TEXTURE_2D, texture.ID)
}

texture2D类用于解析png格式的图片并创建纹理绑定到上下文中

resource.go
package resource

import (
	"io/ioutil"
)

var (
	textures = make(map[string]*Texture2D)
	shaders  = make(map[string]*Shader)
)

func LoadShader(vShaderFile, fShaderFile, name string){
	vertexString, err := ioutil.ReadFile(vShaderFile)
	if err != nil{
        panic(err)
	}
	fragmentString, err := ioutil.ReadFile(fShaderFile)
	if err != nil{
        panic(err)
	}
	shaders[name] = Compile(string(vertexString), string(fragmentString))
}
func GetShader(name string) *Shader{
	return shaders[name]
}

func LoadTexture(TEXTUREINDEX uint32, file, name string){
	texture := NewTexture2D(file, TEXTUREINDEX)
	textures[name] = texture
}
func GetTexture(name string) *Texture2D{
	return textures[name]
}

负责加载纹理和着色器的管理类

2. Object game

First, think about all the elements of the game are the attributes which, and its package, create a game object base class

gameObj.go
package model
import(
	"game2D/resource"
	"game2D/sprite"
	"github.com/go-gl/mathgl/mgl32"
)
type GameObj struct{
	texture *resource.Texture2D
	x float32
	y float32
	size *mgl32.Vec2
	rotate float32
	color *mgl32.Vec3
	isXReverse int32
}
func(gameObj GameObj) GetPosition()mgl32.Vec2{
	return mgl32.Vec2{gameObj.x, gameObj.y}
}
func(gameObj *GameObj) SetPosition(position mgl32.Vec2){
	gameObj.x = position[0]
	gameObj.y = position[1]
}
func(gameObj GameObj) GetSize()mgl32.Vec2{
	return mgl32.Vec2{gameObj.size[0], gameObj.size[1]}
}
func(gameObj *GameObj) Draw(renderer *sprite.SpriteRenderer){
	renderer.DrawSprite(gameObj.texture, &mgl32.Vec2{gameObj.x,gameObj.y}, gameObj.size, gameObj.rotate, gameObj.color,gameObj.isXReverse)
}
func(gameObj *GameObj) ReverseX(){
	gameObj.isXReverse = -1
}
func(gameObj *GameObj) ForWardX(){
	gameObj.isXReverse = 1
}
func NewGameObj(texture *resource.Texture2D, x, y float32, size *mgl32.Vec2, rotate float32, color *mgl32.Vec3) *GameObj{
	return &GameObj{texture:texture,
					x:x,
					y:y,
					size:size,
					rotate:rotate,
					color:color,
					isXReverse:1}
}

Objects within a game there will be those properties? Position, size, appearance (texture), color, and the angle of rotation. Meanwhile For efficiency, a variable identifying whether the added image. With these properties, we will be able to draw directly on the screen.

3. Elf

We already have available for drawing objects game, then naturally he is drawn to the screen, create a sprite drawing class

SpriteRenderer .go
package sprite
import(
	"github.com/go-gl/mathgl/mgl32"
	"game2D/resource"
	"github.com/go-gl/gl/v4.1-core/gl"
)

type SpriteRenderer struct{
	shader *resource.Shader
	vao uint32
}
func NewSpriteRenderer(shader *resource.Shader) *SpriteRenderer{
	spriteRenderer := SpriteRenderer{shader:shader}
	spriteRenderer.initRenderData()
	return &spriteRenderer
}
func(spriteRenderer *SpriteRenderer) DrawSprite(texture *resource.Texture2D, position *mgl32.Vec2, size *mgl32.Vec2, rotate float32, color *mgl32.Vec3,isReverseX int32){
	model := mgl32.Translate3D(position[0], position[1], 0).Mul4(mgl32.Translate3D(0.5*size[0], 0.5*size[1], 0))
	model = model.Mul4(mgl32.HomogRotate3D(rotate, mgl32.Vec3{0, 0, 1}))
	model = model.Mul4(mgl32.Translate3D(-0.5*size[0], -0.5*size[1], 0))
	model = model.Mul4(mgl32.Scale3D(size[0], size[1], 1))
	
	spriteRenderer.shader.SetMatrix4fv("model", &model[0])
	spriteRenderer.shader.SetInt("reverseX", isReverseX)
	spriteRenderer.shader.SetVector3f("spriteColor", *color)
	texture.Use()

	gl.BindVertexArray(spriteRenderer.vao);
    gl.DrawArrays(gl.TRIANGLES, 0, 6);
    gl.BindVertexArray(0);
}
func(spriteRenderer *SpriteRenderer) initRenderData(){
	var vbo uint32
	vertices := []float32{
		0.0, 1.0, 0.0, 1.0,
        1.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 

        0.0, 1.0, 0.0, 1.0,
        1.0, 1.0, 1.0, 1.0,
        1.0, 0.0, 1.0, 0.0,
	}
	gl.GenVertexArrays(1, &spriteRenderer.vao);
    gl.GenBuffers(1, &vbo);

    gl.BindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.BufferData(gl.ARRAY_BUFFER, 4 * len(vertices), gl.Ptr(vertices), gl.STATIC_DRAW);

    gl.BindVertexArray(spriteRenderer.vao);
    gl.EnableVertexAttribArray(0);
	gl.VertexAttribPointer(0, 4, gl.FLOAT, false, 4 * 4, gl.PtrOffset(0));
	gl.BindBuffer(gl.ARRAY_BUFFER, 0);
    gl.BindVertexArray(0);
}

The class action initRenderDatamethod is basically the last article of makeVaofunctions like create vertex buffer objects and vertex array objects and bind it to the context, and in DrawSpritethe pan we be scaled according to the size and location of the object, and tied given object texture to draw on the screen

4. Camera

In the last article, we will abstract the logical user perspective transformation into a camera class, where we also need

camera.go
package camera
import(
	"github.com/go-gl/mathgl/mgl32"
)
type Camera2D struct{
	position,front,up     						   mgl32.Vec3
	movementSpeed         						   float32
	wordWidth,wordHeight,screenWidth,screenHeight  float32
}
func NewDefaultCamera(wordHeight ,wordWidth, screenWidth, screenHeight float32, position2D mgl32.Vec2) *Camera2D{
	position := mgl32.Vec3{position2D[0], position2D[1], 0}
	front    := mgl32.Vec3{0, 0, -1}
	up		 := mgl32.Vec3{0, 1, 0}
	movementSpeed := float32(100)

	return &Camera2D{position:position, 
		front:front, 
		up:up, 
		movementSpeed:movementSpeed,
		wordHeight:wordHeight,
		wordWidth:wordWidth,
		screenHeight:screenHeight,
		screenWidth:screenWidth}
}
//获取摄像头位置
func (camera *Camera2D) GetPosition() mgl32.Vec2{
	return mgl32.Vec2{camera.position[0], camera.position[1]}
}
//获取view
func (camera *Camera2D) GetViewMatrix() *float32{
	target := camera.position.Add(camera.front)
	view := mgl32.LookAtV(camera.position,target, camera.up)
	return &view[0]
}
//重置世界边界
func (camera *Camera2D) resetWordSize(width,height float32){
	camera.wordWidth = width
	camera.wordHeight = height
}
//重设屏幕大小
func (camera *Camera2D) resetScreenSize(width,height float32){
	camera.screenWidth = width
	camera.screenHeight = height
}
//根据坐标转换视野
func(camera *Camera2D) InPosition(x,y float32){
	if(x <= 0){
		camera.position[0] = 0
	}else if(x + camera.screenWidth > camera.wordWidth){
		x = camera.wordWidth - camera.screenWidth
	}else{
		camera.position[0] = x
	}
	if(y <= 0){
		camera.position[1] = 0
	}else if(y + camera.screenHeight > camera.wordHeight){
		camera.position[1] = camera.wordHeight - camera.screenHeight
	}else{
		camera.position[1] = y
	}
}

As the viewing angle can be seen locked in a 2D space, the camera relative to the code on an article has been greatly simplified, and joined the wordWidth,wordHeight,screenWidth,screenHeightlargest four variables used to identify the location of the camera can see, while the new InPositionmethod for Perspective directly converted to a specified location

Basics ends, the next will be able to create a free run of fat and a simple map

Guess you like

Origin blog.csdn.net/qq_35488769/article/details/95043088