SaveGameFree
- Github
- Asset Store
初始化项目
新建项目
替换原有场景
添加物体至场景
调整相机角度
安装插件 SaveGameFree
安装插件 Input System
导入
激活
新建脚本 PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using BayatGames.SaveGameFree;
public class PlayerController : MonoBehaviour
{
public float speed = 10;
private Rigidbody rb;
private float movementX;
private float movementY;
private string identifier = "playerPosition";
// 自定义数据类,保存 player 位置信息
public class PlayerData
{
public float x;
public float y;
}
void Start()
{
GetData();
rb = GetComponent();
}
void Update()
{
// 存档快捷键 q(方便调试,不与 IDE 键位冲突)
if (Input.GetKeyDown(KeyCode.Q))
{
SetData();
}
// 删档快捷键 e(方便调试,不与 IDE 键位冲突)
if (Input.GetKeyDown(KeyCode.E))
{
DeleteData();
}
}
void FixedUpdate()
{
// 组装 3D 向量
Vector3 vector3D = new Vector3(movementX, 0.5f, movementY);
// 为刚体施加力
rb.AddForce(vector3D * speed);
}
void OnMove(InputValue movement)
{
Vector2 vector2D = movement.Get();
movementX = vector2D.x;
movementY = vector2D.y;
}
// 设置 postion
void SetPlayerPosition(float x, float y)
{
transform.position = new Vector3(x, transform.position.y, y);
}
// 存档
void SetData()
{
PlayerData data = new PlayerData();
data.x = transform.position.x;
data.y = transform.position.z;
SaveGame.Save(identifier, data);
}
// 读档
void GetData()
{
// 校验数据是否存在
if (SaveGame.Exists(identifier))
{
PlayerData playerPosition = SaveGame.Load(identifier);
SetPlayerPosition(playerPosition.x, playerPosition.y);
}
}
// 删档
void DeleteData()
{
SaveGame.Clear();
}
}
编辑球体组件
添加组件,以及修改 transform 值
编辑 Player Input 组件
- 新建 Inputs 目录,并保存 input action
- 绑定 input action
运行项目
移动:WSAD / 方向键
读档:自动
存档:Q
删档:E
升级为云存储
云存储功能建议自己实现,重写 SaveGameWeb 类,以及选择更合适的服务端技术选型。以下是官方文档的实现
配置数据库(MySQL)
Git 仓库
Assets/BayatGames/SaveGameFree/Web/Save Game Free - Web/savegamefree.sql
CREATE DATABASE IF NOT EXISTS `savegamefree`;
USE savegamefree;
CREATE TABLE IF NOT EXISTS savegames (
id VARCHAR(255) CHARACTER SET utf8 NOT NULL PRIMARY KEY,
data LONGTEXT CHARACTER SET utf8 NOT NULL
);
初始化项目(Nodejs)
mkdir SaveGameFreeServer
cd SaveGameFreeServer
npm init -y
npm i koa koa-router koa-bodyparser mysql
touch index.js
编写入口文件(index.js)
Git 仓库
Assets/BayatGames/SaveGameFree/Web/Save Game Free - Web/savegamefree.php
以下是基于上述 PHP 脚本改造的Nodejs
实现
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const mysql = require('mysql')
const config = {
portServer: 3000,
database: {
DATABASE: 'savegamefree',
TABLE: 'savegames',
USERNAME: '数据库账号',
PASSWORD: '数据库密码',
HOST: '数据库域名'
}
}
const pool = mysql.createPool({
database: config.database.DATABASE,
user: config.database.USERNAME,
password: config.database.PASSWORD,
host: config.database.HOST
})
class Mysql {
constructor() { }
setPositionById(id, data) {
return new Promise((resolve, reject) => {
pool.query(`INSERT INTO ${config.database.TABLE} (id, data) VALUES ('${id}', ${JSON.stringify(data)}) ON DUPLICATE KEY UPDATE data=VALUES (data)`, function (error, results, fields) {
if (error) {
throw error
}
resolve(results)
})
})
}
getPositionById(id) {
return new Promise((resolve, reject) => {
pool.query(`SELECT data FROM ${config.database.TABLE} WHERE id='${id}'`, function (error, results, fields) {
if (error) {
throw error
}
resolve(results)
})
})
}
}
const app = new Koa()
const router = new Router()
const mysqlInstance = new Mysql()
app
.use(bodyParser())
.use(router.routes())
.use(router.allowedMethods())
router
.post('/savegamefree', async (ctx, next) => {
const postData = ctx.request.body
if (postData.action === 'save') {
const res = await mysqlInstance.setPositionById(postData.identifier, postData.data)
ctx.body = res
}
else if (postData.action === 'load') {
const res = await mysqlInstance.getPositionById(postData.identifier)
ctx.body = res[0] ? res[0].data : null
}
})
app.listen(config.portServer)
console.log(`listening on port ${config.portServer}`)
运行项目
node index
修改脚本 PlayerController.cs(云存储版本)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using BayatGames.SaveGameFree;
public class PlayerController : MonoBehaviour
{
public float speed = 10;
private Rigidbody rb;
private float movementX;
private float movementY;
private string identifier = "playerPosition";
// 自定义数据类,保存 player 位置信息
public class PlayerData
{
public float x;
public float y;
}
void Start()
{
GetDataCloud();
rb = GetComponent();
}
void Update()
{
// 存档快捷键 q(方便调试,不与 IDE 键位冲突)
if (Input.GetKeyDown(KeyCode.Q))
{
SetDataCloud();
}
// 删档快捷键 e(方便调试,不与 IDE 键位冲突)
if (Input.GetKeyDown(KeyCode.E))
{
DeleteData();
}
}
void FixedUpdate()
{
// 组装 3D 向量
Vector3 vector3D = new Vector3(movementX, 0.5f, movementY);
// 为刚体施加力
rb.AddForce(vector3D * speed);
}
void OnMove(InputValue movement)
{
Vector2 vector2D = movement.Get();
movementX = vector2D.x;
movementY = vector2D.y;
}
// 设置 postion
void SetPlayerPosition(float x, float y)
{
transform.position = new Vector3(x, transform.position.y, y);
}
// 云存档
void SetDataCloud()
{
StartCoroutine(SaveEnumerator());
}
// 云读档
void GetDataCloud()
{
transform.localScale = Vector3.zero; // 先隐藏球体,防止异步更新位置 导致球体位置闪烁
StartCoroutine(LoadEnumerator());
}
// http://localhost.charlesproxy.com:3000 映射到 http://localhost:3000
// 防止 charles 抓不到 unity 的请求包
IEnumerator SaveEnumerator()
{
SaveGameWeb web = new SaveGameWeb("usernameFaker", "passwordFaker", "http://localhost.charlesproxy.com:3000/savegamefree");
PlayerData data = new PlayerData();
data.x = transform.position.x;
data.y = transform.position.z;
yield return StartCoroutine(web.Save(identifier, data));
}
IEnumerator LoadEnumerator()
{
SaveGameWeb web = new SaveGameWeb("usernameFaker", "passwordFaker", "http://localhost.charlesproxy.com:3000/savegamefree");
yield return StartCoroutine(web.Download(identifier));
PlayerData defaultData = new PlayerData();
defaultData.x = 0.0f;
defaultData.y = 0.0f;
PlayerData playerPosition = web.Load(identifier, defaultData);
SetPlayerPosition(playerPosition.x, playerPosition.y);
transform.localScale = Vector3.one;
}
}
运行项目
移动:WSAD / 方向键
读档:自动
存档:Q