You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Delphi Tokyo跨平台音频播放方案咨询(Windows/Android/iOS)

解决Delphi Tokyo跨平台扫雷游戏Android端音效延迟问题

我之前在Delphi下做跨平台游戏音效的时候也遇到过一模一样的问题——TMediaPlayer确实不适合短音效的即时播放,它的设计定位是处理长音频文件,加载和解码过程会带来明显的启动延迟。针对Android平台,最靠谱的解决方案是用Android原生的SoundPool类,这玩意儿就是专门为短音效、低延迟播放打造的,完美适配扫雷的点击/触控反馈需求。

核心思路

SoundPool会把短音效预加载到内存中,播放时直接从内存读取,几乎没有延迟。我们可以通过Delphi的JNI桥接调用Android的SoundPool API,同时保留Windows端的PlaySound实现,实现跨平台统一调用。

具体实现步骤

1. 准备并部署音效文件

  • 把你的短音效文件(推荐用WAV格式,解码更快)重命名为小写字母(Android资源名不允许大写),比如click.wavexplode.wav
  • 在Delphi的Deployment管理器中添加这些音效文件,设置:
    • Remote Path: res\raw\
    • Remote Name: 保持和本地文件名一致(比如click.wav

2. 封装SoundPool调用代码

在你的游戏主窗体或专门的音效管理单元中添加以下代码:

unit SoundManager;

interface

uses
  System.Generics.Collections,
  Androidapi.JNI.Media, Androidapi.JNI.JavaTypes, Androidapi.Helpers,
  Winapi.MMSystem;

type
  TSoundManager = class
  private
    FSoundPool: JSoundPool;
    FSoundIDs: TDictionary<string, Integer>;
    FIsLoaded: Boolean;
    procedure OnSoundLoadComplete(SoundPool: JSoundPool; SampleId, Status: Integer);
  public
    constructor Create;
    destructor Destroy; override;
    procedure PlaySound(const ASoundName: string);
    property IsLoaded: Boolean read FIsLoaded;
  end;

var
  GlobalSoundManager: TSoundManager;

implementation

{ TSoundManager }

constructor TSoundManager.Create;
var
  LoadListener: TJSoundPool_OnLoadCompleteListener;
begin
  inherited;
  FSoundIDs := TDictionary<string, Integer>.Create;
  FIsLoaded := False;

  // 初始化SoundPool:最多同时播放3个音效,使用音乐流通道
  FSoundPool := TJSoundPool.JavaClass.init(3, TJAudioManager.JavaClass.STREAM_MUSIC, 0);

  // 设置加载完成监听,确保音效加载完成后再播放
  LoadListener := TJSoundPool_OnLoadCompleteListener.Create;
  LoadListener.onLoadComplete := OnSoundLoadComplete;
  FSoundPool.setOnLoadCompleteListener(LoadListener);

  // 加载音效(这里的TJR.JavaClass.click对应res/raw/click.wav)
  FSoundIDs.Add('click', FSoundPool.load(TAndroidHelper.Context, TJR.JavaClass.click, 1));
  FSoundIDs.Add('explode', FSoundPool.load(TAndroidHelper.Context, TJR.JavaClass.explode, 1));
  // 按需添加更多音效
end;

destructor TSoundManager.Destroy;
begin
  FSoundPool.release;
  FSoundIDs.Free;
  inherited;
end;

procedure TSoundManager.OnSoundLoadComplete(SoundPool: JSoundPool; SampleId, Status: Integer);
begin
  // 检查所有音效是否加载完成(简单判断:这里假设只有两个音效,可根据实际调整)
  FIsLoaded := (FSoundIDs.Count > 0) and (Status = 0);
end;

procedure TSoundManager.PlaySound(const ASoundName: string);
var
  SoundID: Integer;
begin
{$IFDEF MSWINDOWS}
  // Windows端继续使用PlaySound
  PlaySound(PChar(ASoundName), 0, SND_RESOURCE or SND_ASYNC);
{$ENDIF}
{$IFDEF ANDROID}
  if FIsLoaded and FSoundIDs.TryGetValue(ASoundName, SoundID) then
  begin
    // 播放音效:音量左右声道1.0,优先级1,不循环,播放速率1.0
    FSoundPool.play(SoundID, 1.0, 1.0, 1, 0, 1.0);
  end;
{$ENDIF}
end;

initialization
  GlobalSoundManager := TSoundManager.Create;

finalization
  GlobalSoundManager.Free;

end.

3. 在游戏中调用音效

比如在点击/触控事件中:

procedure TMainForm.MineButtonClick(Sender: TObject);
begin
  // 播放点击音效
  GlobalSoundManager.PlaySound('click');
  // 其他游戏逻辑...
end;

关键注意事项

  • Android资源名必须是小写字母、数字和下划线,不能有大写或特殊字符,否则无法通过TJR.JavaClass访问。
  • 建议在程序启动时就初始化TSoundManager,让音效有足够时间预加载。
  • 如果音效较多,可以在OnSoundLoadComplete中统计加载完成的数量,确保所有音效加载完毕后再允许用户操作。

内容的提问来源于stack exchange,提问作者Alister

火山引擎 最新活动