ebiten 实现角色移动
文章目录
构思如何实现角色移动
- 总所周知,游戏中的角色移动依靠的是图片的变动,在快速变换的情况下,达到人物移动的效果.类似于翻书动画
- 游戏开发中常用到的图片一般是精灵图,比如将人物的动作放在同一图片的图片,不了解精灵图的可以自行了解一下
- 介绍到这里,开始在go中实现角色移动
- 1.定义角色的结构体(精灵图,宽度,高度,移动速度…)
- 2.定义角色移动图片帧数的参数结构体(起点x,起点y,宽度,高度)
- 3.将是参数输入到帧数参数数组
- 4.实现角色行走动作,实现角色真实移动逻辑代码
- 5.实现角色行走动作,实现角色真实移动渲染代码
- 6.初始化数据
素材
效果展示
1.定义角色的结构体
type Player struct{
image *ebiten.Image //精灵图
x float64 //人物生成横坐标
y float64 //人物生成纵坐标
speed float64 //人物移动速度
}
type Game struct{
role *Player
frameCounter int
}
2.定义角色移动图片帧数的参数结构体
type Frame struct{
x int//起点x
y int //起点y
width int //宽度
height int //高度
}
3.将参数输入到帧数参数数组
- 如何测量帧数图片的参数:
- 我自己是打开ps一点一点测量的,不知道各位有什么更好的方法
// 跑步定义动画帧
var frames = []Frame{
{21, 34, 25, 30},
{88, 34, 25, 30},
{151, 34, 25, 30},
{213, 34, 25, 30},
{280, 34, 25, 30},
{343, 34, 25, 30},
}
4.实现角色行走动作,实现角色真实移动逻辑代码
- 本人觉得人物的动作和行走的关系类似于,一个相框和图片的关系,相框移动代表角色移动,图片更换代表角色的动作
- 这里我将动画帧函数和更新角色位置分开写进Update函数当中
- 此处的
g.frameCounter++,animationCounter++
用于减缓角色移动的速度以及动画更新的速度,不添加的情况下,角色会很鬼畜以及移动很快,对应数值越大就越慢,这个就类似一定时器一般,只有达到条件才能执行代码 - 参数介绍
fromTurn
用于跑动作和其他动作的交换moveSpeed
是帧数数组的下标
- 当检测到有左右和上按键时,就增加一次下标,要注意不要越界访问
- 当检测到下按键时,就减少一次下标,实现倒退的感觉
- 角色移动就是普通的增加和减少坐标实现角色移动
- 注意:上下的控键要分开写,直接实现可以往斜方向走
func (g *Game) Update() error {
g.frameCounter++
animationCounter++
// 更新动画帧
if animationCounter >= 4 {
animationCounter = 0//及时置零
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyUp) {
fromTurn = 1
moveSpeed += 1
//防越界访问
if moveSpeed == 6 {
moveSpeed = 0
}
} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
fromTurn = 1
moveSpeed -= 1
if moveSpeed == -1 {
moveSpeed = 5
}
} else {
fromTurn = 0
}
}
// 更新角色位置
if g.frameCounter >= 2 {
g.frameCounter = 0
if ebiten.IsKeyPressed(ebiten.KeyRight) {
g.role.x += g.role.speed
} else if ebiten.IsKeyPressed(ebiten.KeyLeft) {
g.role.x -= g.role.speed
} else {
g.role.x += 0
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
g.role.y -= g.role.speed
} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
g.role.y += g.role.speed
} else {
g.role.y += 0
}
}
return nil
}
5. 实现角色行走动作,实现角色真实移动渲染代码
ebitenutil.NewImageFromFile
:获取精灵图,注意将图片路径更换imgRun.SubImage()
获取精灵图的子图- 参数:
image.Rect(frame.X, frame.Y, frame.X+frame.Width, frame.Y+frame.Height)
- 对应左上角和右下角坐标,达到只渲染精灵图的某一部分,最后强转为
*ebiten.Image
- 参数:
optsRun := &ebiten.DrawImageOptions{}
optsRun.GeoM.Translate(g.role.x, g.role.y)
实现人物的移动,参数是人物的坐标
func (g *Game) drawRunning(screen *ebiten.Image) {
// 跑步精灵图的绘制代码
imgRun, _, errRun := ebitenutil.NewImageFromFile("G:\\Golang\\ebitenCode\\resource\\R2302082\\Pixel Crawler - FREE - 1.8\\Heroes\\Knight\\Run\\Run-Sheet.png")
if errRun != nil {
panic(errRun)
}
//获取当前的动画帧
frame := frames[moveSpeed]
//实现人物动作的渲染
subImageRun := imgRun.SubImage(image.Rect(frame.X, frame.Y, frame.X+frame.Width, frame.Y+frame.Height)).(*ebiten.Image)
//实现人物移动
optsRun := &ebiten.DrawImageOptions{}
optsRun.GeoM.Translate(g.role.x, g.role.y)
//渲染
screen.DrawImage(subImageRun, optsRun)
}
6.初始化数据
func NewGame() *Game {
return &Game{
role: &Player{nil, 100, 100, 3},
}
}
注意:
1.在需要将图片地址换成你本机的相对地址或绝对地址(我相对老是添加不对,求各位教教)
2.完整代码中包含另外一套动作,希望各位自己动手利用顶部两张图片实现代码
完整代码
package main
import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"image"
)
// Player 角色定义
type Player struct {
image *ebiten.Image // 精灵图
x float64 // 精灵图横坐标
y float64 // 精灵图纵坐标
speed float64 // 精灵图移动速度
}
type Frame struct {
X, Y, Width, Height int
}
// 跑步定义动画帧
var frames = []Frame{
{21, 34, 25, 30},
{88, 34, 25, 30},
{151, 34, 25, 30},
{213, 34, 25, 30},
{280, 34, 25, 30},
{343, 34, 25, 30},
}
// IdIes 空闲动画
var IdIes = []Frame{
{5, 2, 20, 30},
{37, 2, 20, 30},
{69, 2, 20, 30},
{101, 2, 20, 30},
}
// 动画
var (
moveSpeed = 0
IdIeSpeed = 0
)
var fromTurn = 0
type Game struct {
role *Player
frameCounter int
}
// (爱死)
// 添加一个新的变量来控制动画帧更新速度
var animationCounter = 0
var IdleCounter = 0
func (g *Game) Update() error {
g.frameCounter++
animationCounter++
// 更新动画帧
if animationCounter >= 4 {
animationCounter = 0
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyUp) {
fromTurn = 1
moveSpeed += 1
if moveSpeed == 6 {
moveSpeed = 0
}
} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
fromTurn = 1
moveSpeed -= 1
if moveSpeed == -1 {
moveSpeed = 5
}
} else {
fromTurn = 0
}
}
// 更新角色位置
if g.frameCounter >= 2 {
g.frameCounter = 0
if ebiten.IsKeyPressed(ebiten.KeyRight) {
g.role.x += g.role.speed
} else if ebiten.IsKeyPressed(ebiten.KeyLeft) {
g.role.x -= g.role.speed
} else {
g.role.x += 0
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
g.role.y -= g.role.speed
} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
g.role.y += g.role.speed
} else {
g.role.y += 0
}
}
//休闲动作
if fromTurn == 0 {
IdleCounter++
if IdleCounter >= 9 {
IdleCounter = 0
IdIeSpeed += 1
if IdIeSpeed == 4 {
IdIeSpeed = 0
}
}
}
return nil
}
// 跑步和空闲
func (g *Game) drawRunning(screen *ebiten.Image) {
// 跑步精灵图的绘制代码
imgRun, _, errRun := ebitenutil.NewImageFromFile("G:\\Golang\\ebitenCode\\resource\\R2302082\\Pixel Crawler - FREE - 1.8\\Heroes\\Knight\\Run\\Run-Sheet.png")
if errRun != nil {
panic(errRun)
}
frame := frames[moveSpeed]
subImageRun := imgRun.SubImage(image.Rect(frame.X, frame.Y, frame.X+frame.Width, frame.Y+frame.Height)).(*ebiten.Image)
optsRun := &ebiten.DrawImageOptions{}
optsRun.GeoM.Translate(g.role.x, g.role.y)
screen.DrawImage(subImageRun, optsRun)
}
func (g *Game) drawIdle(screen *ebiten.Image) {
// 空闲精灵图的绘制代码
imgIdIe, _, errIdIe := ebitenutil.NewImageFromFile("G:\\Golang\\ebitenCode\\resource\\R2302082\\Pixel Crawler - FREE - 1.8\\Heroes\\Knight\\Idle\\Idle-Sheet.png")
if errIdIe != nil {
panic(errIdIe)
}
IdIe := IdIes[IdIeSpeed]
subImageIdIe := imgIdIe.SubImage(image.Rect(IdIe.X, IdIe.Y, IdIe.Width+IdIe.X, IdIe.Height+IdIe.Y)).(*ebiten.Image)
optsIdIe := &ebiten.DrawImageOptions{}
optsIdIe.GeoM.Translate(g.role.x, g.role.y)
screen.DrawImage(subImageIdIe, optsIdIe)
}
func (g *Game) Draw(screen *ebiten.Image) {
if fromTurn == 1 {
//跑步精灵图
g.drawRunning(screen)
} else if fromTurn == 0 {
//空闲精灵图
g.drawIdle(screen)
}
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 640 * 2 / 3, 480 * 2 / 3
}
func NewGame() *Game {
//img := ebiten.NewImage(25, 30)
return &Game{
role: &Player{nil, 100, 100, 3},
}
}
func main() {
ebiten.SetWindowTitle("实现角色移动")
ebiten.SetWindowSize(640, 480)
//启动游戏
game := NewGame()
if err := ebiten.RunGame(game); err != nil {
panic(err)
}
}