在我们的2D游戏里,游戏地图完全由方块构成,因此首先要基于前文的GameObj
派生出一个block
对象表示方块
package model
const(
BlockHeight float32 = 15
BlockWidth float32 = 20
)
//方块类
type Block struct{
GameObj
}
目前为止,方块类和普通游戏类没有任何区别,但后期可能会添加诸如方块硬度之类的属性
接下来可以开始开发地图类了
package model
import(
“game2D/resource”
“game2D/sprite”
“math”
“fmt”
“github.com/go-gl/mathgl/mgl32”
)
type GameMap struct{
Height float32
Width float32
blocks [][] *Block
heightBlockNum int
widthBlockNum int
}
//一个简单的测试用的游戏地图生成函数
func NewGameMap(width,height float32, mapFile string) *GameMap{
heightBlockNum := int(math.Ceil(float64(height / BlockHeight)))
widthBlockNum := int(math.Ceil(float64(width / BlockWidth)))
grounds := heightBlockNum / 4
xGrounds := widthBlockNum / 4
fmt.Println(“map block size:”,heightBlockNum,widthBlockNum)
blocks := make([][]*Block,heightBlockNum)
for i := 0;i < heightBlockNum;i++{
rowBlocks := make([]*Block,widthBlockNum)
if(i < grounds || i > grounds*3){
for j := 0; j < widthBlockNum;j++{
if(j < xGrounds || j > xGrounds * 3 || i == 0){
gameObj := NewGameObj(resource.GetTexture(“soil”),float32(j) * BlockWidth,float32(i)*BlockHeight,&mgl32.Vec2{BlockWidth,BlockHeight},0,&mgl32.Vec3{1,1,1})
rowBlocks[j] = &Block{GameObj:*gameObj}
}
}
}
blocks[i] = rowBlocks
}
return &GameMap{Height:height,
Width:width,
blocks:blocks,
heightBlockNum:heightBlockNum,
widthBlockNum:widthBlockNum}
}
//将一个物体坐标转换为地图格子坐标范围
func (gameMap *GameMap) FetchBox(position,size mgl32.Vec2)(int,int,int,int){
startY := int(math.Floor(float64((position[0]) / gameMap.Width * float32(gameMap.widthBlockNum))))-1;
if(startY <= 0){
startY = 0
}
endY := int(math.Ceil(float64((position[0] + size[0]) / gameMap.Width * float32(gameMap.widthBlockNum)))) + 1
if(endY >= gameMap.widthBlockNum){
endY = gameMap.widthBlockNum - 1
}
startX := int(math.Floor(float64((position[1]) / gameMap.Height * float32(gameMap.heightBlockNum)))) -1
if(startX < 0){
startX = 0
}
endX := int(math.Ceil(float64((position[1] + size[1]) / gameMap.Height * float32(gameMap.heightBlockNum)))) +1
if(endX >= gameMap.heightBlockNum){
endX = gameMap.heightBlockNum - 1
}
return startX,endX,startY,endY
}
//渲染地图
func (gameMap *GameMap) Draw(position mgl32.Vec2, zoom mgl32.Vec2, renderer *sprite.SpriteRenderer){
startX,endX,startY,endY := gameMap.FetchBox(position,zoom)
for i:=startX;i<=endX;i++{
for j := startY;j<endY;j++{
block := gameMap.blocks[int(i)][int(j)]
if(block != nil){
block.Draw(renderer)
}
}
}
}
在上述的地图类中,我们用一个二维切片储存地图中的所有方块。用地图长宽除以方块长宽获得横向和竖向方块数量然后往二维数组中放入方块。要注意的是,虽然可以在渲染函数中简单的遍历并调用block来绘制地图,但试想一下如果地图长一千个方块宽一千个方块会怎么样?每一帧都要绘制一百万个方块!这显然是不合理的,所以在渲染函数中我们先将位置转化为方块位置,然后根据屏幕大小计算出我们能看到的方块位置和数量并对其进行渲染,这样地图无论多大我们都只用渲染屏幕内的方块,大大优化了效率
二.碰撞的艺术
单纯的碰撞检测其实并不复杂,简单的AABB模型(不发生旋转的矩形之间)只需要初中数学水平就能理解,判断两个矩形的x轴和y轴是否重叠即可,难点在于如何检测碰撞方向。
如图所示,根据xy轴是否重叠能很简单的判断出某个状态下两个物体是否已经发生了碰撞,如果我们是要做一个类似flappy bird
或者跑酷类游戏,那到这一步就可以了。但如果我们要做的是一个类似泰拉瑞亚的游戏,这样显然不符合要求。可以想一下,如果一个游戏人物向屏幕左下方移动,在某个时刻撞到了一堵垂直的墙,这时候虽然人物无法向左继续移动,但应该能向上或向下移动,如果撞到的是一堵水平的墙,那就应该反过来。我相信一定会有一个巧妙的数学方法能解决这个问题,但作者数学水平实在太次,只能想个笨办法了。
首先要明确,在游戏中,每个时刻每个物体的运动都是可预测的,在每一帧绘制之前,我们会根据物体的运动速度和方向计算好该物体在这一帧的位移,然后才会交由显卡绘制。我的笨办法是,在每个位移发生之前先获取位移方向并将物体沿着这个方向迭代,每次迭代里移动一小段的距离,如果在某次迭代中发生碰撞,那上一次的迭代位置就应该是物体最终所处的方向,明白了这一点,我们可以开始写代码了
package physic
import(
“github.com/go-gl/mathgl/mgl32”
“math”
)
//检测两个矩形是否发生碰撞
func IsCollidingAABB(thisGameObj,anotherObj React) bool{
tPosition := thisGameObj.GetPosition()
tSize := thisGameObj.GetSize()
aPosition := anotherObj.GetPosition()
aSize := anotherObj.GetSize()
return isCollidingReact(tPosition,tSize,aPosition,aSize);
}
type React interface{
GetPosition() mgl32.Vec2
GetSize() mgl32.Vec2
}
func isCollidingReact(position1,size1,position2,size2 mgl32.Vec2) bool{
// x轴方向碰撞?
collisionX := position1[0] + size1[0] >= position2[0] && position2[0] + size2[0] >= position1[0]
// y轴方向碰撞?
collisionY := position1[1] + size1[1] >= position2[1] && position2[1] + size2[1] >= position1[1]
return collisionX && collisionY
}
//检测两个矩形运动后是否会发生碰撞
func WillCollidingAABB(thisGameObj,anotherObj React,dt mgl32.Vec2) bool{
tPosition := thisGameObj.GetPosition().Sub(dt)
tSize := thisGameObj.GetSize()
aPosition := anotherObj.GetPosition()
aSize := anotherObj.GetSize()
return isCollidingReact(tPosition,tSize,aPosition,aSize);
}
//检测两个矩形的碰撞,并获取碰撞位置
func ColldingAABBPlace(thisGameObj,anotherObj React,shift mgl32.Vec2) (bool,mgl32.Vec2){
position := thisGameObj.GetPosition()
if(shift[0] == 0 && shift[1] == 0){
return false, position
}
colldingShift := mgl32.Vec2{0.0}
colldingDt := shift.Normalize()
for math.Abs(float64(colldingShift[0])) <= math.Abs(float64(shift[0])) && math.Abs(float64(colldingShift[1])) <= math.Abs(float64(shift[1])){
tempColldingShift := colldingShift.Sub(colldingDt)
if(WillCollidingAABB(thisGameObj,anotherObj,tempColldingShift)){
return true,thisGameObj.GetPosition().Sub(colldingShift)
}
colldingShift = tempColldingShift
}
return false,thisGameObj.GetPosition()
}
现在我们有了能检测两个物体是否发生碰撞和碰撞位置的检测方法,然后该怎么用到我们的应用中?物体运动之前把地图中的每个方块都检测一次显然不现实,那只检测屏幕内的的方块合理吗?答案是不合理,因为不考虑bug(比如速度太快穿过去了)的情况下,运动物体只可能和他周围的方块发生碰撞,所以我们应该只对物体周围的方块进行碰撞检测
我们将碰撞逻辑加入到地图类中,添加方法
//检测一个物体是否与地图中的方块发生碰撞
func (gameMap *GameMap) IsColl(gameObj GameObj,shift mgl32.Vec2)(bool,mgl32.Vec2){
position := gameObj.GetPosition();
size := gameObj.GetSize()
startX,endX,startY,endY := gameMap.FetchBox(mgl32.Vec2{position[0],position[1]},mgl32.Vec2{size[0],size[1]})
for i:=startX;i<=endX;i++{
for j := startY;j<endY;j++{
block := gameMap.blocks[int(i)][int(j)]
if(block != nil){
isCol,position := physic.ColldingAABBPlace(gameObj,block,shift)
if(isCol){
return isCol,position
}
}
}
}
return false,gameObj.GetPosition()
}
三.主角的诞生
现在要来创造我们的主角了,首先创建一个类代表游戏中所有可移动的物体
package model
import(
“game2D/resource”
“game2D/constant”
“github.com/go-gl/mathgl/mgl32”
)
//可移动的游戏对象
type MoveObj struct{
GameObj
//在上下左右方向是否可移动
stockUp,stockDown,stockLeft,stockRight bool
//水平移动速度
movementSpeed float32
//飞行速度
fallSpeed float32
//下坠速度
flySpeed float32
//移动时的动画纹理
moveTextures []*resource.Texture2D
//静止时的纹理
stantTexture *resource.Texture2D
//游戏地图
gameMap *GameMap
//当前运动帧
moveIndex int
//运动帧之间的切换阈值
moveDelta float32
}
func NewMoveObject(gameObj GameObj,movementSpeed,flySpeed float32, moveTextures []*resource.Texture2D,gameMap *GameMap) *MoveObj{
moveObj := &MoveObj{GameObj:gameObj,
movementSpeed:movementSpeed,
fallSpeed:100,
gameMap:gameMap,
moveTextures:moveTextures,
flySpeed:flySpeed,
moveIndex:0,
moveDelta:0,
stantTexture:gameObj.texture}
return moveObj
}
//恢复静止
func (moveObj *MoveObj) Stand(){
moveObj.texture = moveObj.stantTexture
}
//由用户主动发起的运动
func(moveObj *MoveObj) Move(direction constant.Direction, delta float32){
shift := mgl32.Vec2{0,0}
if(direction ==constant. DOWN){
if(!moveObj.stockDown && moveObj.y + moveObj.size[1] < moveObj.gameMap.Height){
shift[1] += moveObj.flySpeed * delta
}
}
if(direction == constant.UP){
if(!moveObj.stockUp && moveObj.y > 0){
shift[1] -= moveObj.flySpeed * delta
}
}
if(direction == constant.LEFT){
moveObj.ReverseX()
if(moveObj.moveIndex >= len(moveObj.moveTextures)){
moveObj.moveIndex = 0
}
moveObj.moveDelta += delta
if(moveObj.moveDelta > 0.1){
moveObj.moveDelta = 0
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
如果你也是看准了Python,想自学Python,在这里为大家准备了丰厚的免费学习大礼包,带大家一起学习,给大家剖析Python兼职、就业行情前景的这些事儿。
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、全套PDF电子书
书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。
四、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
成为一个Python程序员专家或许需要花费数年时间,但是打下坚实的基础只要几周就可以,如果你按照我提供的学习路线以及资料有意识地去实践,你就有很大可能成功!
最后祝你好运!!!
sdnimg.cn/img_convert/ec690501ea1dbe2cb209cbf4013c2477.png)
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
成为一个Python程序员专家或许需要花费数年时间,但是打下坚实的基础只要几周就可以,如果你按照我提供的学习路线以及资料有意识地去实践,你就有很大可能成功!
最后祝你好运!!!