JAVA一分钟游戏编程-游戏动画(06)

1. 目标

一分钟实现游戏动画

2. 游戏动画基本要素

在写代码之前,我们先来了解一下游戏动画的基本要素。

云顶山,开源仙门。

一群练气期弟子聚在一起。

王富贵:“既然是是动画,它肯定能动!”

张平安:“😓,听君一席话,如听一席话,动画是多张图片,或者说多帧之间不停地切换。”

李吉祥:“👏🏻,张师兄说的有理,动画还应该具备帧率,游戏帧率控制在60帧,大多数动画不需要这么高的帧率,10帧左右即可。”

王富贵:“哈哈,刚刚我和大家开玩笑呢,我也想到了一点,角色应该具备状态,比如站立、行走、跳跃等等,当状态改变时,动画也随之改变。”

屏幕前的你立刻一拍大腿,觉得这三人说的很有道理。

于是,你默默地移动鼠标,往下翻开了代码的具体实现……

3. 具体实现

稍微改造一下测试类,为了可读性,我们这里抽取出来三个方法init、update、render,这三个方法只关注游戏逻辑怎么写。

package com.sandbox;

import com.pinea.event.PineaEvent;
import com.pinea.render.PineaImage;
import com.pinea.shape.PineaRect;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 导入 Pinea 引擎核心类的静态方法
import static com.pinea.core.Pinea.*;

public class Main {

    long startTime; // 记录程序启动时间,用于动画帧计算

    // 构造方法:初始化游戏窗口
    public Main() {
        pineaInit("PineaTest", 800, 600); // 初始化窗口,标题和尺寸
        startTime = System.currentTimeMillis(); // 记录启动时间
        init(); // 初始化游戏资源
    }

    // 游戏主循环
    public void run() {
        PineaEvent event = new PineaEvent(); // 事件对象,用于处理输入

        // 时间控制变量,用于稳定帧率
        long lastTime = System.nanoTime();
        long accTime = 0;
        long currentTime;
        long deltaTime = 1000_000_000 / 60; // 60FPS 的每帧纳秒数
        float deltaTimeF = 1f / 60f; // 每帧的时间(秒)

        pineaShow(); // 显示窗口
        boolean running = true;
        while (running) { // 主循环
            // 处理事件队列
            while (pineaPollEvent(event)) {
                if (event.type == PINEA_EVENT_QUIT) { // 窗口关闭事件
                    running = false;
                } else if (event.type == PINEA_EVENT_KEY_DOWN) { // 按键按下
                    Input.update(event.key, true);
                } else if (event.type == PINEA_EVENT_KEY_UP) { // 按键释放
                    Input.update(event.key, false);
                }
            }

            // 帧率控制:固定 60FPS 更新
            currentTime = System.nanoTime();
            accTime += currentTime - lastTime;
            lastTime = currentTime;
            if (accTime >= deltaTime) {
                while (accTime >= deltaTime) {
                    accTime -= deltaTime;
                    update(deltaTimeF); // 更新游戏逻辑
                }
                render(); // 渲染画面
                pineaRender(); // 提交渲染
            }
        }

        pineaQuit(); // 退出游戏
    }

    // 游戏资源
    PineaImage bgImage; // 背景图片
    Map<String, List<PineaImage>> playerAnimations; // 玩家动画集合(状态 -> 帧列表)
    PineaRect playerPosition; // 玩家位置和大小
    String playerState; // 玩家当前状态(idle/run/jump)
    boolean flipX; // 玩家是否水平翻转

    // 初始化游戏资源
    private void init() {
        bgImage = pineaLoadImage("assets/images/bg.png"); // 加载背景图

        // 加载玩家图片并切割动画帧
        PineaImage playerImage = pineaLoadImage("assets/images/mario.png");
        playerAnimations = new HashMap<>();
        // idle动画帧
        playerAnimations.put("idle", Arrays.asList(
                playerImage.crop(16 * 6, 16 * 2, 16, 16).scale(2.68f, 2.68f)
        ));
        // idle( flip)动画帧
        playerAnimations.put("flip_idle", Arrays.asList(
                playerImage.crop(16 * 6, 16 * 2, 16, 16).scale(2.68f, 2.68f).flip(true, false)
        ));
        // 跑步动画帧
        playerAnimations.put("run", Arrays.asList(
                playerImage.crop(16 * 0, 16 * 2, 16, 16).scale(2.68f, 2.68f),
                playerImage.crop(16 * 1, 16 * 2, 16, 16).scale(2.68f, 2.68f),
                playerImage.crop(16 * 2, 16 * 2, 16, 16).scale(2.68f, 2.68f)
        ));
        // 跑步( flip)动画帧
        playerAnimations.put("flip_run", Arrays.asList(
                playerImage.crop(16 * 0, 16 * 2, 16, 16).scale(2.68f, 2.68f).flip(true, false),
                playerImage.crop(16 * 1, 16 * 2, 16, 16).scale(2.68f, 2.68f).flip(true, false),
                playerImage.crop(16 * 2, 16 * 2, 16, 16).scale(2.68f, 2.68f).flip(true, false)
        ));
        // 跳跃动画帧
        playerAnimations.put("jump", Arrays.asList(
                playerImage.crop(16 * 4, 16 * 2, 16, 16).scale(2.68f, 2.68f)
        ));
        // 跳跃( flip)动画帧
        playerAnimations.put("flip_jump", Arrays.asList(
                playerImage.crop(16 * 4, 16 * 2, 16, 16).scale(2.68f, 2.68f).flip(true, false)
        ));

        playerState = "idle"; // 初始状态为 idle
        playerPosition = new PineaRect(0, 0, 0, 0); // 初始位置
    }

    // 更新游戏逻辑
    private void update(float deltaTimeF) {
        playerState = "idle"; // 默认状态为 idle

        // 根据按键更新玩家状态和位置
        if (Input.isKeyPressed(PINEA_KEY_A)) { // 左移
            playerPosition.x -= 250.f * deltaTimeF;
            playerState = "run";
            flipX = true;
        }
        if (Input.isKeyPressed(PINEA_KEY_D)) { // 右移
            playerPosition.x += 250.f * deltaTimeF;
            playerState = "run";
            flipX = false;
        }
        if (Input.isKeyPressed(PINEA_KEY_W)) { // 上移
            playerPosition.y -= 250.f * deltaTimeF;
            playerState = "run";
        }
        if (Input.isKeyPressed(PINEA_KEY_S)) { // 下移
            playerPosition.y += 250.f * deltaTimeF;
            playerState = "run";
        }
        if (Input.isKeyPressed(PINEA_KEY_SPACE)) { // 跳跃
            playerState = "jump";
        }
        // 如果翻转,后续就播放翻转动画
        if (flipX) {
            playerState = "flip_" + playerState;
        }
    }

    // 渲染画面
    private void render() {
        pineaDrawImage(bgImage, 0, 0); // 绘制背景

        // 根据当前状态获取动画帧列表
        List<PineaImage> pineaImages = playerAnimations.get(playerState);
        // 计算当前应该显示的帧索引(基于运行时间)
        long spendTime = System.currentTimeMillis() - startTime;
        int index = (int) spendTime / 100 % pineaImages.size();
        // 绘制玩家当前帧
        pineaDrawImage(pineaImages.get(index), playerPosition.x, playerPosition.y);
    }

    // 程序入口
    public static void main(String[] args) {
        new Main().run();
    }
}

保存代码,运行结果如下:

张平安、李吉祥立刻震惊,不约而同发出感叹:“还真是牛犊子从哪里来啊!!”

王富贵疑惑:“什么意思?”

张平安解释:“🐮🍺”

王富贵:“emm…”

就在这时,一道轰隆声响彻天际,云顶山突然剧烈地摇晃起来。

远处天空,有裂纹蔓延而开,裂隙间,漆黑之物若泼墨倒灌向这片大地,状如飞瀑,颇为震撼。

张平安微微一怔:“不好,“赞印”和“藏印”过少,低维空间的封印有所松动。”

片刻后,只见他缓缓抬起一根手指,突然指向屏幕外正在阅读此文章的大帅比:“快救救我,哥们。另外,帮我敲打一下作者,我怀疑他有凑字数的嫌疑。。。”

3. To be Continued…

本节已结束。

我们已经通过Swing实现了自己的游戏库,得益于Java的跨平台性,Pinea可以运行在Windows、Mac、linux上。

于是,你有了更多的野心:我不仅要跨平台,还要跨语言,还要性能好,还要……

想要跨语言,底层最好是C/C++库,不管是Lua、Java、Python还是C#,都能直接或间接的调用C/C++库。

SDL3是一个纯C实现、性能好且稳定的库。

接下来,我们将大刀阔斧、釜底抽薪、心如刀绞地用Java Native形式,来实现游戏的底层逻辑,换句话说,我们做SDL3的Java绑定。

年轻不折腾,折腾你到老😏——偷偷学编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值