Phira-docs Instruction

This is the instruction of Phira-related documentation, visit GitHub to submit feedbacks or contribute, read in other language by editing the url, for example /en/

If you’re willing to provide translation, please refer to

Provide the translated .po file in the Pull Request, and add the corresponding language code in LANGUAGES in env in the .github/mdbook.yml file, visit ISO 639 for language code reference

Respack

In prpr, you can customize the respack. The respack consists of how the notes look, particle effects, hit sound effects, etc. You can find premade respacks, or you choose to make one yourself. The file structure of the respack is explained in detailed below.

Structure

A Respack is a single zip archive containing the configuration file info.yml and other resource files. Some of the resource files are required and some are optional.

Resource files

The resource files must include:

  • click.png and click_mh.png: The note skin for click notes, mh represents simultaneous clicks.
  • drag.png and drag_mh.png: The note skin for drag notes, mh represents simultaneous drags.
  • flick.png and flick_mh.png: The note skin for flick notes, mh represents simultaneous flicks.
  • hold.png and hold_mh.png: The note skin for hold notes, mh represents simultaneous holds.
  • hit_fx.png: The image for hit effect.

The resource files can also include (If they are not included, the default will be used):

  • click.ogg, drag.ogg and flick.ogg: The sound effects for the corresponding notes, note that the sampling rate must be 44100 Hz, or else it will cause crashes during rendering (prpr-render).
  • ending.mp3: The background music for the score screen.

Configuration file

The configuration file uses yml format, with the following required fields (using a default respack as an example):

name: Default
author: "Mivik & MisaLiu"
hitFx: [5, 6]
holdAtlas: [50, 50]
holdAtlasMH: [50, 110]
  • name: The name of the respack.
  • author: The author/creator of the respack.
  • description: The description/introduction of the respack.
  • hitFx: The frame count of the width and height of the hit effect. Hit effects are stored as multiple frames in a single image, thus, it is necessary to specify the number of horizontal and vertical frames in the image. For example, in this image, the horizontal and vertical frame count is 5 and 6 respectively (the last row is not very visible, but it is present there) (the image uses a black background for the ease of viewing, but a transparent background should be used when creating a respack).
  • holdAtlas: The height of the tail and head of the hold image. The note skin of the hold is an image, it has the tail section, middle section and the head section, from top to bottom of the image. The 2 numbers of holdAtlas specify the height of the tail and head respectively in the image. For example, in this image, the height of the tail and head section are both 50 pixels.
  • holdAtlasMH: Similar to the previous bullet point, specifies the related details for simultaneous holds.

In addition, there are these optional fields:

  • hitFxDuration (float, default value: 0.5): The duration of the hit effect in seconds.
  • hitFxScale (float, default value: 1.0): The scale of the hit effect.
  • hitFxRotate (bool, default value: false): Whether the hit effect rotates with the note.
  • hitFxTinted (bool, default value: true): Whether the hit effect is colored according to the color of the judgement line.
  • hideParticles (bool, default value: false): Whether the hit effect particles are hidden.
  • holdKeepHead (bool, default value: false): Whether to keep showing the hold’s head section once it touches the line.
  • holdRepeat (bool. default value: false): Whether the middle section of the hold is repetitively stretched. The below shows 3 images, the hold’s image on the left, the image of how the hold will look like when holdRepeat is false on the middle, and the image of how the hold will look like when holdRepeat is true on the right.
  • holdCompact (bool, default value: false): Whether to overlap the hold’s head and tail section with the middle section. Using the previous example, if holdCompact is false, the hold will look like the image on the left, you can see that the tail and head section are separated from the middle section. If holdCompact is true, the hold will look like the image on the middle and right.
  • colorPerfect (Hexadecimal color code, default value: 0xe1ffec9f): The color of the judgement line when it is still AP (All Perfect).
  • colorGood (Hexadecimal color code, default value: 0xebb4e1ff): The color of the judgement line when it is still FC (Full Combo).

Chart Feature

prpr not only supports the official Phigros, 'pec', and 'rpe' chart format, but also added some unique features. These chart features can only be used in prpr and other clients that built on top of prpr. These features include:

The configuration of chart features need to be saved in extra.json which can be found in the root directory of the zip file. In order to use the chart features, you need to edit the JSON file manually. If you don't know what JSON is, see JSON Tutorial.

BPM Config

Before making any configurations, you need to set the BPM of the song/track in extra.json. For example:

{
   "bpm": [
      { "time": [0, 0, 1], "bpm" : 200.0 },
      { "time": [10, 1, 2], "bpm" : 250.0 }
   ],
   ...
}

This shows that the song has an initial BPM of 200, the BPM then changes to 250 at the 10.5 beat.

Animation variables

Animation variables are often used in charts. For example, in RPE, the X coordinate change event is actually the X coordinate as an animation variable.

There are 2 formats for prpr’s animation variables. The first is for a constant value, which means that the variable will not change and will always have the same value. This format is applied just for the ease of filling up the fields. The second format is the same as the one in RPE and is composed of multiple Event. The format of a single Event is shown below:

{
	"startTime": [0, 0, 1],
	"endTime": [0, 0, 1],
	"easingType": 2,
	"easingLeft": 0.0,
	"easingRight": 1.0,
	"start": ...,
	"end": ...
}

where startTime and endTime are the event’s start time and end time respectively; easingType is the type of easing; easingLeft and easingRight are optional, it represents the start and end position of the ease.

start and end represent the values at the beginning and end of the time. The type of the value here depends on the type of the animation variable itself, which can be any of the following types:

  • float: a single decimal.
  • vec2: 2 decimal representing a 2D vector, represented as an array.
  • color: 4 integers (0-255) that represents a color, the format is [R, G, B, A].

For example, the following animation variable will vary linearly from 0 to 1 over a given time period:

[
	{
		"startTime": [2, 0, 1],
		"endTime": [4, 0, 1],
		"easingType": 2,
		"start": 0.0,
		"end": 0.1
	}
]

Effects

Effect is a feature that applies shader to the entire chart for a specified amount of time. They can be used to achieve visual effects that are impossible or difficult to achieve in vanilla charts. prpr has some premade shaders, or you can also write your own shaders. For more details, see Writing your own shader.

Format

You need to add the effects field in extra.json, whose content is an array of Effect.

Effect

The format of a single Effect is shown below:

{
	"start": [0, 0, 1],
	"end": [0, 0, 1],
	"shader": "Name of shader",
	"global": false,
	"vars": {
		...
	}
}

start and end specify the start and end time respectively in the same format as RPE's default time format ([bar number, numerator, denominator]).

shader represents the shader's name, it can be a built-in shader, or a custom shader.

global determines whether the effect will affect UI elements (combo count, pause button, etc). This field can be left blank, and its default value is false.

vars is an optional parameter, it is a mapping of variable names to animation variables, it is used to specify the parameters of the shader (or rather: shader's uniform variables). i.e. suppose I need to specify the values of 2 variables, power and radius, I can write it like this:

{
	...,
	"vars": {
		"power": ...,
		"radius": ...
	}
}

Example

The following example will apply the built-in chromatic shader from [2, 0, 1] to [4, 0, 1] and will vary the power parameter linearly from 0 to 0.1 in this time period, with the sampleCount fixed at 5:

{
	...,
	"effects": [
		{
			"start": [2, 0, 1],
			"end": [4, 0, 1],
			"shader": "chromatic",
			"vars": {
				"power": [
					{
						"startTime": [2, 0, 1],
						"endTime": [4, 0, 1],
						"easingType": 2,
						"start": 0.0,
						"end": 0.1
					}
				],
				"sampleCount": 5
			}
		}
	]
}

Built-in shaders

You can view all the built-in shaders, along with their parameters and related descriptions, at here.

Built-in shaders

This section lists prpr's built-in shaders.

chromatic

Chromatic effect.

example

Parameters

  • sampleCount (integer, default value: 3, Range: 1-64): Sample count, the higher the value, the display will be more coherent, and the overhead will be higher.
  • power (float, default value: 0.01): The intensity, or offset distance.

circleBlur

Dot blurring. Will enlarge the pixel blur as highlight, recommended to use with parameter.

example

Parameters

  • size (float, default value: 10.0): The size of the origin, in pixels.

fisheye

Fisheye effect. When power is negative, you can make tunneling effect.

power as negative value:

example

power as positive value:

example

Parameters

  • power (float, default value: -0.1): Scale of fisheye effect.

glitch

Confusion/Error effect. The effect produces an indeterminate flicker over time.

example

Parameters

  • power (float, default value: 0.3): Flicker intensity.
  • rate (float, default value: 0.6, range: 0-1): Flicker frequency. 0 for no flickers, 1 for always flicker.
  • speed (float, default value: 5.0): Speed of flickering animation.
  • blockCount (float, default value: 30.5): Look at above images, number of misaligned strips (approximate).
  • colorRate (float, default value: 0.01, range: 0-1): Distance of color difference.

grayscale

Grayscale effect.

example

Parameters

  • factor (float, default value: 1.0): Grayscale level. 0 for no grayscale processing, 1 for complete grayscale processing.

noise

Noise effect. Applies a random layer of blur to the screen.

example

Parameters

  • seed (float, default value: 81.0): Used to generate random patterns. By varying the seeds continuously, the pattern can be changed continuously.
  • power (float, default value: 0.03, range: 0-1): Look at above images, intensity of blur (Range of pixel offset).

pixel

Pixelated effect.

example

Parameters

  • size (float, default value: 10.0): Size of individual pixels after pixelation.

radialBlur

Radial blur.

example

Parameters

  • centerX (float, default value: 0.5, range: 0-1): X coordinate of the effect center.
  • centerY (float, default value: 0.5, range: 0-1): Y coordinate of the effect center.
  • power (float, default value: 0.01, range: 0-1): Intensity of effect.
  • sampleCount (integer, default value: 3, range: 1-64): Sample count, the higher the value, the display will be more coherent, and the overhead will be higher.

shockwave

Shockwave effect. Recommended to use with progress parameter.

example

Parameters

  • progress (float, default value: 0.2, range: 0-1): Progress of the effect. Shockwave is an animation, 0 for start of animation, 1 for end of animation).
  • centerX (float, default value: 0.5, range: 0-1): X coordinate of the effect center.
  • centerY (float, default value: 0.5, range: 0-1): Y coordinate of the effect center.
  • width (float, default value: 0.1): Width of the effect.
  • distortion (float, default value: 0.8): Intensity of distortion.
  • expand (float, default value: 10.0): Breadth of expansion.

vignette

Virtual lighting effect. Will darken or tint the edges of the screen to other colors, can be used to simulate some of Arcaea's anomaly effect.

example

Parameters

  • color (color, default color: black): Color at the edges of the screen.
  • extend (float, default value: 0.25, range: 0-1): The extension from the edge, the larger the value, the more black parts.
  • radius (float, default value: 15.0): Size of central light, the smaller the value, the greater the range affacted.

Writing your own shaders

Fragment shaders are used for making the special effects. You can write your own shaders and attach them to the chart's zip file. In the shader field, you need to fill in the /shader_path. Note that the / must be included and this field must be filled in because it distinguishes the built-in shaders from the custom shaders.

For compatibility, GLSL 1.00 is used.

Built-in variables

prpr has the following variables used for shaders:

varying vec2 uv; // UV of texture
uniform vec2 screenSize; // Size of the screen (Note that it refers to the entire screen, and not just the chart/gameplay area)
uniform sampler2D screenTexture; // Texture of the screen (Refers to the texture of the entire screen, and not just the chart/gameplay area)
uniform float time; // The chart's time in seconds

There are also variables that exists, but these variables are useless in the fragment shaders. When defining your own variables, you should avoid these variables' name:

uniform mat4 Model;
uniform mat4 Projection;
uniform vec2 UVScale;

Shader variables

In order to define your own parameters in the chart, you need to define your shader uniform variables like this:

uniform type name; // %def%

where type is the variable type, currently float, vec2, and vec4 are supported; name is the variable name; def is the default value. Any of the 3 values cannot be left out.

Example

The example below shows that the shader will overlay the screen with varying shades of red depending on the value of factor:

#version 100
precision mediump float;

varying lowp vec2 uv;
uniform sampler2D screenTexture;

uniform float factor; // %0.5% 0..1

void main() {
  gl_FragColor = mix(texture2D(screenTexture, uv), vec4(1.0, 0.0, 0.0, 1.0), factor);
}

Video Background

In prpr, you can play a video in the background, but the audio will not be played. However, since most videos have a large file size and the upload limit for prpr charts is 10MB, charts that have large BGA may not be uploaded. If you need to have a video background, it is recommended that the video background is used for small animations and compressed (including but not limited to removing audio).

Format

You need to add a videos field in the extra.json file, whose content is an array of Video.

Video

The format for a single Video is as shown below:

{
	"path": "bga.mp4",
	"time": [0, 0, 1],
	"scale": "cropCenter",
	"alpha": 1.0,
	"dim": 0.3
}

The path field is mandatory to be filled in, and it should point to the file path of the video, the other four fields are optional.

time indicates the start time of the video in terms of beat numbers.

scale indicates how the video is scaled. When making a chart, you need to consider how the chart will look at different aspect ratios. For this purpose, the scale field has 3 options available:

  • cropCenter (default): Scales up the video uniformly until it fills the screen.
  • inside: Scales down the video uniformly, ensures that the entire video is shown within the screen.
  • fit: Forcefully stretch the video to the entire screen.

alpha and dim represents the transparency of the video (if it is invisible, the song background art will be displayed instead) and dimness respectively, they are both Animation variables.

Unlock animation

Please note the difference between unlock animation and video background

The unlock animation allows a video to be played before the player's initial playthrough of the chart.

Usage

The UI is currently not ready and is slightly cumbersome. The following steps are recommended for Windows.

If your chart already has info.yml, please skip to step three.

  1. Import your chart in Phira.
  2. Search for your chart ID in data/charts/custom (e.g. 9067228), locate the corresponding folder, find info.yml in the folder, and copy it to your chart folder (Note that the folder mentioned here is not the folder that you get after importing into Phira, it's the folder that you extracted from pez).
  3. Add the unlock animation to your chart folder (Note that the folder mentioned here is not the folder that you get after importing into Phira, it's the folder that you extracted from pez), assuming the file name is unlock.mp4.
  4. Find the line unlockVideo: null in info.yml and replace it with unlockVideo: unlock.mp4 (or add that line if it's not there).
  5. Re-import your chart.

UML documentation

UML 文件是 UI 描述文件, 可以用来创建于代码解耦的可定制界面.

Grammar

UML 文件格式是纯文本的人类可读格式. UML 由 Element(元素), 注释和变量定义语句构成, 每个元素占据一或多行.

Coordinates

坐标原点在进入活动页后下滑一屏后的左上角处, x 轴向右, y 轴向下.

屏幕宽度总是 2, 可以通过宽高比计算屏幕高度, 参见内置变量.

Data type

UML 中有如下数据类型:

Float

单精度浮点数, 任意的数字都是 Float.

Rect

矩形. 由 [left, top, width, height](分别是左上角 x 坐标, 左上角 y 坐标, 矩形宽度, 矩形高度)定义.

Rect 在定义后可以访问一些只读属性, 具体包括:

  • Rect.lRect.x: 左上角 x 坐标
  • Rect.tRect.y: 左上角 y 坐标
  • Rect.w: 矩形宽度
  • Rect.h: 矩形高度
  • Rect.r: 右下角 x 坐标
  • Rect.b: 右下角 y 坐标
  • Rect.cx: 中心 x 坐标
  • Rect.cy: 中心 y 坐标

Bool

布尔值. 值是 truefalse, 目前只能用于元素的属性中.

String

字符串. 用双引号括起来的文本, 用于表示按钮行为, 颜色, URL, 具体如下:

  • Color: 颜色. 可以是十六进制 RGB 或 ARGB 颜色值(如 "#ff0000""#7fffffff")或颜色名(如 "red"). 目前可用的颜色名有:
    • "white"
    • "black"
    • "red"
    • "blue"
    • "yellow"
    • "green"
    • "gray"
  • Action: 按钮行为. 可用的值有:
    • "join": 参与该活动
    • "open:url": Opens the specified URL
  • URL: 网址

Expression

表达式是由 Float, 变量, 运算符和函数组成的. 表达式经过计算是 Float 类型的值.

Operators

The operators that can be used are:

  • +: Addition
  • -: Subtraction
  • *: Multiplication
  • /: Division
  • (): Parentheses, used to change the order of operations
  • ==: Is equal to, 1 if true, 0 otherwise.
  • ! =: If not equal, value is 1 if true, 0 otherwise.
  • >: Is more than, 1 if true, 0 otherwise.
  • <: Is less than, 1 if true, 0 otherwise.
  • >=: Is more than or equal to, 1 if true, 0 otherwise.
  • <=: Is less than or equal to, 1 if true, 0 otherwise.

函数

可以使用的函数有:

  • sin(x): 正弦函数
  • cos(x): 余弦函数
  • tan(x): 正切函数
  • abs(x): Absolute value function
  • exp(x): 指数函数
  • atan2(x): 反正切函数
  • ln(x): 自然对数函数
  • sig(x): 符号函数, x+0 或正数时返回 1, 为 -0 或负数时返回 -1
  • step(x, y): 阶跃函数, x < y0 时返回 1, 否则返回 0
  • floor(x): 向下取整函数
  • ceil(x): 向上取整函数
  • round(x): 四舍五入函数
  • max(x, y...): 最大值函数
  • min(x, y...): 最小值函数
  • clamp(x, min, max): 将 x 限制在 minmax 之间

其中三角函数和反三角函数使用弧度制.

Variable

变量是 UML 中可以被读取或者写入的值, 定义方式如下:

let name = value

定义一个变量 name, 其值为 value. value 可以是 FloatRect 类型的值, 或结果是 FloatRect 类型的表达式.

变量可以被重复定义, 但只有最后一次定义的值会被使用. 定义变量后, 你可以在新建元素或定义其他变量时使用这个变量.

Built-in variables

UML 内置了如下变量:

  • t: 当前时间, 单位为秒
  • top: 实际高度与实际宽度之比
  • o: 滑动距离
  • joined: 是否已经参与了该活动, 参与了时值为 1, 否则为 0
  • $h: The maximum height that the user can scroll, limit the user's scrolling range by setting the value of this variable

UML 并不具备事件功能, 可以利用 t, o按钮的按下时刻来为页面添加动态效果, 如动画, 换页等.

全局变量

全局变量是在 UML 文件的任何地方都可以被读取或者写入的变量. 全局变量的定义方式如下:

global name = @type

其中, type 只能为 btn. 此后, 你仍然可以通过 let 的方式写入全局变量, 只有最后一次定义的值会被使用.

活动界面在每一帧都会被重画, 这意味着 UML 在每一帧都会重新计算所有变量的值.

按钮的一些属性需要在帧之间保持不变, 因此需要使用全局变量.

Element

元素是活动界面中的可视或可交互的对象, 定义方式如下:

type(attr1: value1, attr2: value2, ...) { Hello! }

其中, type 为元素的类型, attrNvalueN 分别为属性名称和属性值. 不同类型的元素的属性也各不相同, 其中一些是必填, 会在介绍元素时具体标注.

元素的 id 属性为所有元素共有的可选属性, 在元素被定义后, 其 id 属性将自动对应一个表示该元素绘制范围的 Rect 变量.

后面的 { Hello } 是可选内容, 代表该元素的文字内容. 只对 p(段落元素)生效.

Paragraph Element p

p(id, x, y, ax, ay, size, ml, mw, bl, c) {
   Text
}
  • id(选填): 元素 ID
  • x(选填, 默认为 0): 文本 x 坐标
  • y(选填, 默认为 0): 文本 y 坐标
  • ax(选填, 默认为 0): 文本横向对齐, [0, 1] 间的浮点数. 为 0 时, 按照 x 坐标左对齐, 为 1 时右对齐, 0.5 时居中
  • ay(选填, 默认为 0): 文本纵向对齐, 具体意义同上
  • size(选填, 默认为 1): 文本大小
  • ml(选填, 默认为 false): 文本是否多行渲染, 只在设置了 mw 的条件下有效
  • mw(选填): 文本最大宽度. 在单行模式下, 超出范围的文本将被省略;多行模式下将自动换行
  • bl(选填, 默认为 true): 纵向对齐时是否按照基准线对齐
  • c(选填, 默认为 "white"): 文本颜色
  • Text(必填): 文本内容

p 的位置并不通过完整的矩形来定义, 但你仍可以用 pid 属性来获取其矩形区域.

Image Element img

img(id, url, r, c, t)
  • id(选填): 元素 ID
  • url(必填): 图片 URL, 用双引号括起来
  • r(必填): 图片显示的位置矩形, 类型为 Rect
  • c(选填, 默认为 "white"): 在图片上叠加的颜色. 若颜色半透明, 图片也会半透明
  • t(选填, 默认为 cropCenter): 图片的裁剪方式. 可选值有:
    • cropCenter: 保持图片比例, 从图片中心裁剪,保证 r 被完全填满
    • inside: 保持图片比例, 使图片完全显示在 r
    • fit: 拉伸为 r 的大小

Chart Collection Element col

col(id, cid, r, rn, rh)
  • id(选填): 元素 ID
  • cid(必填): 谱面合集 ID
  • r(必填): 谱面合集显示的位置矩形, 类型为 Rect
  • rn(选填, 默认为 4): 一行显示的谱面数
  • rh(选填, 默认为 0.3): 谱面高度

Button Element btn

btn(id, r, action)
  • id(选填): 元素 ID
  • r(必填): 按钮显示的位置矩形, 类型为 Rect
  • action(选填): 按钮的点击行为, 可用的值参见数据类型

在元素被定义后, 其 id 属性除了表示该元素绘制范围的 Rect 变量, 还具有以下属性:

  • id.pressing: 按钮是否正在被按下, 被按下时值为 1, 否则为 0
  • id.last: 按钮上次被按下的时刻, 从未被按下时值为 -1
  • id.count: 按钮被按下的次数

Comment

你可以用 # 开头的行添加注释, 除了部分特殊格式的注释(注释表达式), 注释不会被执行, 可以用来提示当前代码的作用.

注释行在 # 后至少有一个字符, 否则会导致报错.

Comment Expression

注释表达式是一种特殊的注释, 它是为了兼容旧版本设计的, 可以被执行的注释. 注释表达式的格式如下:

#>exp(attr1: value1, attr2: value2, ...)

其中, exp 表示该表达式的类型, 括号内的是该表达式的属性, 是可选内容, attrNvalueN 分别为属性名称和属性值. 不同类型的表达式的属性也各不相同, 其中一些是必填, 会在介绍表达式时具体标注.

下面逐个介绍各类型的注释表达式:

结束表达式 #>pop

#>pop

注释表达式以行为单位, 作用于数行内的元素, 变量等, 其作用范围是从表达式所在行开始到下一个 #>pop 表达式所在行结束. 因此, 多数其他表达式后面都必须跟一个 #>pop 表达式, 表示结束当前表达式的作用范围.

旋转表达式 #>rot

#>rot(angle, cx, cy)
  • angle(必填): 旋转角度, 单位为弧度
  • cx(必填): 旋转中心 x 坐标
  • cy(必填): 旋转中心 y 坐标

平移表达式 #>tr

#>tr(dx, dy)
  • dx(选填, 默认为 0): 横向平移距离
  • dy(选填, 默认为 0): 纵向平移距离

透明度表达式 #>alpha

#>alpha(a)
  • a(optional, default 0): transparency, range is [0, 1], 0 is fully transparent, 1 is fully opaque.

矩阵表达式 #>mat

#>mat(x00, x01, x02, x03, x10, x11, x12, x13, x20, x21, x22, x23, x30, x31, x32, x33)
  • x00(选填, 默认为 0): 矩阵第一行第一列的值
  • x01(选填, 默认为 0): 矩阵第一行第二列的值
  • x02(选填, 默认为 0): 矩阵第一行第三列的值
  • x03(选填, 默认为 0): 矩阵第一行第四列的值
  • x10(选填, 默认为 0): 矩阵第二行第一列的值
  • x11(选填, 默认为 0): 矩阵第二行第二列的值
  • x12(选填, 默认为 0): 矩阵第二行第三列的值
  • x13(选填, 默认为 0): 矩阵第二行第四列的值
  • x20(选填, 默认为 0): 矩阵第三行第一列的值
  • x21(选填, 默认为 0): 矩阵第三行第二列的值
  • x22(选填, 默认为 0): 矩阵第三行第三列的值
  • x23(选填, 默认为 0): 矩阵第三行第四列的值
  • x30(选填, 默认为 0): 矩阵第四行第一列的值
  • x31(选填, 默认为 0): 矩阵第四行第二列的值
  • x32(选填, 默认为 0): 矩阵第四行第三列的值
  • x33(选填, 默认为 0): 矩阵第四行第四列的值

可以通过矩阵表达式实现元素的缩放, 平移, 旋转等变换.

条件表达式 #>if

#>if(condition)

#>elif(condition)

#>else

#>fi
  • condition(必填): 条件表达式, 可以是任意表达式, 当值为 0 表示假, 否则表示真.

需要注意的是 #>if 并不适用 #>pop 来结束, 而是使用 #>fi.

低版本兼容表达式 #>if-no-v2

#>if-no-v2

如果你的 UML 文件中使用了 V2 版本中没有的特性, 可以在使用这个表达式显示部分元素, 以提示使用低版本的 Phira 客户端的用户尽快升级.

注释表达式是 V2 版本的新功能, 只在 V2 版本中有效, 在 V1 版本中会被当作一般注释忽略. #if-no-v2 表达式实际上的作用是忽略其作用范围内的内容.

How to Debug

编译 phira-main 时加入参数 --features event_debug, 然后在 Phira 客户端可执行文件同目录下新建名为 test.uml 的文件. 此后进入任意一个活动, 页面内容将与 test.uml 保持实时同步.

Example UML

提供了几个 UML 的样例, 可供参考.

Event Template

此文件为 sjfhsjfh 于 2024-02-07 凌晨为 _2024 羽笙杯_创建的活动文件, 鉴于活动方要求过于抽象, sjfhsjfh 决定写一个仅包含基本功能的页面.

UML 源码

2024 Xmas

这是 sjfhsjfh 在

UML 源码

let w = 1920
let h = 1080
let rh = 2 * top

let bg_rect = [0, 0 - rh + o, 2, rh]

img(id: bg, url: "https://files-cf.phira.cn/events/xmas-2023-kedmue/bg.jpeg", r: bg_rect, t: cropCenter)

# Back button
let back_btn_rect = [bg_rect.x + 100 / w * 2, bg_rect.t + 50 / h * rh, -45 / w * 2, 75 / h * 1.18]
img(id: back_btn_img, url: "https://files-cf.phira.cn/ltc-arrow.png", r: back_btn_rect, t: fit)
btn(id: back_btn, r: back_btn_rect, action: "exit")

let chart_h = 0.4 * top

# left-top
let dx1 = 0.003 * sin(90 * t) + 0.01 * sin(11.5 * (1 + t / 100000) * t)
let dy1 = 0.004 * sin(87 * t) + 0.008 * sin(11.3 * (1 + t / 10000) * t)
let s1 = 1 + 0.03 * sin(10 * exp(1.01 * ln(t)) - 2 * t)
let col_r1 = [bg_rect.l + 0.87 + dx1 + 0.45 * (1 - s1), bg_rect.t + 0.452 * rh + dy1 + chart_h * (1 - s1), 0.45 * s1, chart_h * s1]
col(id: col_xmas, cid: 12762, r: col_r1, rn: 1, rh: chart_h * s1)

# left-bottom
let dx2 = 0.003 * sin(89 * t) + 0.01 * sin(12 * (1 + t / 99000) * t)
let dy2 = 0.004 * sin(88 * t) + 0.0069 * sin(15 * (1 + t / 11000) * t)
let s2 = 1 + 0.03 * sin(10.45 * exp(0.99 * ln(t)) + t)
let col_r2 = [bg_rect.l + 1.02 + dx2 + 0.45 * (1 - s2), bg_rect.t + 0.72 * rh + dy2 + chart_h * (1 - s2), 0.45 * s2, chart_h * s2]
col(id: col_xmas, cid: 12769, r: col_r2, rn: 1, rh: chart_h * s2)

# right-top
let dx3 = 0.003 * sin(91 * t) + 0.01 * sin(12.5 * (1 + t / 109000) * t)
let dy3 = 0.0035 * sin(86 * t) + 0.0082 * sin(11.6 * (1 + t / 9900) * t)
let s3 = 1 + 0.03 * sin(10.2 * exp(1.02 * ln(t)) - 3 * t)
let col_r3 = [bg_rect.l + 1.42 + dx3 + 0.45 * (1 - s3), bg_rect.t + 0.49 * rh + dy3 + chart_h * (1 - s3), 0.45 * s3, chart_h * s3]
col(id: col_xmas, cid: 12770, r: col_r3, rn: 1, rh: chart_h * s3)

let $h = 0

Advanced skills

UML 是一种非常简单的语言, 但是通过组合简单的元素, 我们也能实现一些较为复杂的效果.

Page Switch

如果我们希望活动界面包括多个页面, 可以通过在超出屏幕的部分放置其他页面的内容, 并通过整个页面所有元素的移动达到类似页面切换的效果.

例如, 我们可以这样放置横纵六个页面的内容:

            |-----------|
            |   3, 2    |
            |           |
            |-----------|
            |   2, 2    |
            |           |
|-----------|-----------|-----------|-----------|
|   1, 1    |   1, 2    |   1, 3    |   1, 4    |
|           |           |           |           |
|-----------|-----------|-----------|-----------|

可以在每个页面的左上角放置一个 o 方便计算每个页面上的元素的位置:

# o
let tmp_rect = [page_offset_x_1 + page_offset_x_2 + page_offset_x_3, page_offset_y_1 + page_offset_y_2, 0.1, 0.1]
img(id: o11, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 2, o11.t, 0.1, 0.1]
img(id: o12, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 4, o11.t, 0.1, 0.1]
img(id: o13, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 6, o11.t, 0.1, 0.1]
img(id: o14, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

#>if(page_offset_y_1_ratio)
let tmp_rect = [o12.l, o11.t - 2 * top, 0.1, 0.1]
img(id: o22, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)
#>fi

#>if(page_offset_y_2_ratio)
let tmp_rect = [o12.l, o11.t - 4 * top, 0.1, 0.1]
img(id: o32, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)
#>fi

在以上的定义中, 我们通过 page_offset_x_npage_offset_y_n 定义了 o11 的偏移量, 由于其他页面的偏移量都是相对于 o11 的, 所以在 o11 移动时, 其他页面的元素也会跟着移动.

此外, 我们还通过 #>if 设置了 o22o32 绘制的时机. 这是为了防止纵向切换后, 用户在活动首屏看到当前页面上方页面的内容. 通过这个值, 我们可以将页面上方的内容移出屏幕.

为了得出这些偏移量的具体值, 我们需要使用定义一些用于切换页面的按钮, 并通过按钮的 last 属性计算出偏移量的值.

global btn_r_1 = @btn
global btn_l_2 = @btn

global btn_r_2 = @btn
global btn_l_3 = @btn

global btn_r_3 = @btn
global btn_l_4 = @btn

global btn_r_3 = @btn
global btn_l_4 = @btn

global btn_u_1 = @btn
global btn_d_2 = @btn

global btn_u_2 = @btn
global btn_d_3 = @btn

# btn_lr_1234
let tmp_rect = [o12.l - 0.1, o11.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_1_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_1, r: tmp_rect, t:fit)

let tmp_rect = [o12.l, o12.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_2, r: tmp_rect, t:fit)

let tmp_rect = [o13.l - 0.1, o12.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_2, r: tmp_rect, t:fit)

let tmp_rect = [o13.l, o13.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_3, r: tmp_rect, t:fit)

let tmp_rect = [o14.l - 0.1, o13.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_3, r: tmp_rect, t:fit)

let tmp_rect = [o14.l, o14.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_4_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_4, r: tmp_rect, t:fit)

# btn_ud_123
let tmp_rect = [o12.l + 1 - 0.05, o12.t, 0.1, 0.1]
img(id: btn_u_1_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/up_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_u_1, r: tmp_rect, t:fit)

#>if(page_offset_y_1_ratio)
let tmp_rect = [o22.l + 1 - 0.05, o12.t - 0.1, 0.1, 0.1]
img(id: btn_d_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/down_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_d_2, r: tmp_rect, t:fit)

let tmp_rect = [o22.l + 1 - 0.05, o22.t, 0.1, 0.1]
img(id: btn_u_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/up_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_u_2, r: tmp_rect, t:fit)
#>fi

#>if(page_offset_y_2_ratio)
let tmp_rect = [o32.l + 1 - 0.05, o22.t - 0.1, 0.1, 0.1]
img(id: btn_d_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/down_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_d_3, r: tmp_rect, t:fit)
#>fi

其中按钮 id 最后的编号表示按钮所在的页面.

在定义了按钮后, 我们可以通过按钮的 last 属性计算出偏移量的值:

let animation_duration = 0.7
let animation_speed = 1 / animation_duration

let a = btn_r_1.last
let b = btn_l_2.last
# back and forth
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)

# a > b ? x : y
let page_offset_x_1_ratio = (a > b) * x + (a <= b) * y

# easeInOutQuad
let x = 4 * page_offset_x_1_ratio * page_offset_x_1_ratio * page_offset_x_1_ratio
let y = 1 - (-2 * page_offset_x_1_ratio + 2) * (-2 * page_offset_x_1_ratio + 2) * (-2 * page_offset_x_1_ratio + 2) / 2
let page_offset_x_1_ratio_eased = (page_offset_x_1_ratio < 0.5) * x + (page_offset_x_1_ratio >= 0.5) * y
let page_offset_x_1 = page_offset_x_1_ratio_eased * -2

关于纵向切换的偏移量,我们可以通过类似的方法计算出.

这样, 我们就完成了页面切换效果.

完整的 UML 代码如下:

let animation_duration = 0.7
let animation_speed = 1 / animation_duration

global btn_r_1 = @btn
global btn_l_2 = @btn

global btn_r_2 = @btn
global btn_l_3 = @btn

global btn_r_3 = @btn
global btn_l_4 = @btn

global btn_r_3 = @btn
global btn_l_4 = @btn

global btn_u_1 = @btn
global btn_d_2 = @btn

global btn_u_2 = @btn
global btn_d_3 = @btn

# ---
let a = btn_r_1.last
let b = btn_l_2.last
# back and forth
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)

# a > b ? x : y
let page_offset_x_1_ratio = (a > b) * x + (a <= b) * y

# easeInOutQuad
let x = 4 * page_offset_x_1_ratio * page_offset_x_1_ratio * page_offset_x_1_ratio
let y = 1 - (-2 * page_offset_x_1_ratio + 2) * (-2 * page_offset_x_1_ratio + 2) * (-2 * page_offset_x_1_ratio + 2) / 2
let page_offset_x_1_ratio_eased = (page_offset_x_1_ratio < 0.5) * x + (page_offset_x_1_ratio >= 0.5) * y
let page_offset_x_1 = page_offset_x_1_ratio_eased * -2
# ---

# ---
let a = btn_r_2.last
let b = btn_l_3.last
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)
let page_offset_x_2_ratio = (a > b) * x + (a <= b) * y

let x = 4 * page_offset_x_2_ratio * page_offset_x_2_ratio * page_offset_x_2_ratio
let y = 1 - (-2 * page_offset_x_2_ratio + 2) * (-2 * page_offset_x_2_ratio + 2) * (-2 * page_offset_x_2_ratio + 2) / 2
let page_offset_x_2_ratio_eased = (page_offset_x_2_ratio < 0.5) * x + (page_offset_x_2_ratio >= 0.5) * y
let page_offset_x_2 = page_offset_x_2_ratio_eased * -2
# ---

# ---
let a = btn_r_3.last
let b = btn_l_4.last
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)
let page_offset_x_3_ratio = (a > b) * x + (a <= b) * y

let x = 4 * page_offset_x_3_ratio * page_offset_x_3_ratio * page_offset_x_3_ratio
let y = 1 - (-2 * page_offset_x_3_ratio + 2) * (-2 * page_offset_x_3_ratio + 2) * (-2 * page_offset_x_3_ratio + 2) / 2
let page_offset_x_3_ratio_eased = (page_offset_x_3_ratio < 0.5) * x + (page_offset_x_3_ratio >= 0.5) * y
let page_offset_x_3 = page_offset_x_3_ratio_eased * -2
# ---

# ---
let a = btn_u_1.last
let b = btn_d_2.last
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)
let page_offset_y_1_ratio = (a > b) * x + (a <= b) * y

let x = 4 * page_offset_y_1_ratio * page_offset_y_1_ratio * page_offset_y_1_ratio
let y = 1 - (-2 * page_offset_y_1_ratio + 2) * (-2 * page_offset_y_1_ratio + 2) * (-2 * page_offset_y_1_ratio + 2) / 2
let page_offset_y_1_ratio_eased = (page_offset_y_1_ratio < 0.5) * x + (page_offset_y_1_ratio >= 0.5) * y
let page_offset_y_1 = page_offset_y_1_ratio_eased * 2 * top
# ---

# ---
let a = btn_u_2.last
let b = btn_d_3.last
let x = min((t - a) * animation_speed, 1)
let y = max(1 - (t - b) * animation_speed, 0)
let page_offset_y_2_ratio = (a > b) * x + (a <= b) * y

let x = 4 * page_offset_y_2_ratio * page_offset_y_2_ratio * page_offset_y_2_ratio
let y = 1 - (-2 * page_offset_y_2_ratio + 2) * (-2 * page_offset_y_2_ratio + 2) * (-2 * page_offset_y_2_ratio + 2) / 2
let page_offset_y_2_ratio_eased = (page_offset_y_2_ratio < 0.5) * x + (page_offset_y_2_ratio >= 0.5) * y
let page_offset_y_2 = page_offset_y_2_ratio_eased * 2 * top
# ---

# o
let tmp_rect = [page_offset_x_1 + page_offset_x_2 + page_offset_x_3, page_offset_y_1 + page_offset_y_2, 0.1, 0.1]
img(id: o11, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 2, o11.t, 0.1, 0.1]
img(id: o12, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 4, o11.t, 0.1, 0.1]
img(id: o13, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

let tmp_rect = [o11.l + 6, o11.t, 0.1, 0.1]
img(id: o14, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)

#>if(page_offset_y_1_ratio)
let tmp_rect = [o12.l, o11.t - 2 * top, 0.1, 0.1]
img(id: o22, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)
#>fi

#>if(page_offset_y_2_ratio)
let tmp_rect = [o12.l, o11.t - 4 * top, 0.1, 0.1]
img(id: o32, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/blank.png", r: tmp_rect, t:fit)
#>fi

# btn_lr_1234
let tmp_rect = [o12.l - 0.1, o11.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_1_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_1, r: tmp_rect, t:fit)

let tmp_rect = [o12.l, o12.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_2, r: tmp_rect, t:fit)

let tmp_rect = [o13.l - 0.1, o12.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_2, r: tmp_rect, t:fit)

let tmp_rect = [o13.l, o13.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_3, r: tmp_rect, t:fit)

let tmp_rect = [o14.l - 0.1, o13.t + top - 0.05, 0.1, 0.1]
img(id: btn_r_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/right_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_r_3, r: tmp_rect, t:fit)

let tmp_rect = [o14.l, o14.t + top - 0.05, 0.1, 0.1]
img(id: btn_l_4_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/left_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_l_4, r: tmp_rect, t:fit)

# btn_ud_123
let tmp_rect = [o12.l + 1 - 0.05, o12.t, 0.1, 0.1]
img(id: btn_u_1_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/up_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_u_1, r: tmp_rect, t:fit)

#>if(page_offset_y_1_ratio)
let tmp_rect = [o22.l + 1 - 0.05, o12.t - 0.1, 0.1, 0.1]
img(id: btn_d_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/down_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_d_2, r: tmp_rect, t:fit)

let tmp_rect = [o22.l + 1 - 0.05, o22.t, 0.1, 0.1]
img(id: btn_u_2_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/up_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_u_2, r: tmp_rect, t:fit)
#>fi

#>if(page_offset_y_2_ratio)
let tmp_rect = [o32.l + 1 - 0.05, o22.t - 0.1, 0.1, 0.1]
img(id: btn_d_3_bkg, url: "https://teamflos.github.io/phira-docs/assets/uml/advanced/page_switch/down_arrow.png", r: tmp_rect, t:fit)
btn(id: btn_d_3, r: tmp_rect, t:fit)
#>fi

let $h = 2 * top - 0.02

构建指南

什么,移动设备?,我不会,长大后再学习
Windows:here
Linux:here

Cargo 安装

Windows

  1. 点击 这里 下载构建工具安装程序。

  2. 双击打开 rustup-init.exe 后出现安装窗口;输入2 然后输入 y 然后再次输入 2 ,然后输入 x86_64-pc-windows-gnu ,最后一路回车开始安装,直到输出 Rust is installed now. Great!

    • 注意:千万不要直接回车安装 MSVC,这在后续构建将会出现大量问题!
  3. 前往 MSYS2 官网下载 MSYS2 安装程序,下载完成后双击打开,如无特殊需求,一路下一步即可,直到提示 Finished the MSYS2 Setup,点击右下角的按钮后将弹出一个窗口,输入以下指令,安装过程一路回车即可。

    pacman -Sy && pacman -Syu

    pacman -S mingw-w64-x86_64-toolchain

    • 如果您无法连接到 github,您也可以去 缓存站 下载 MSYS2, 注意,缓存站只能保证您可以下载,不能高速下载,也不一定是最新版。
  4. 打开命令提示符(cmd)或 PowerShell,输入 cargo -V 检查是否成功安装,若返回版本号则安装成功,若出现其他提示,请见 Windows 常见问题。

  5. 按下图所示,修改环境变量 sys_path_I sys_path_II sys_path_III sys_path_IV sys_path_V

  6. 打开命令提示符(cmd)或 PowerShell,输入 gcc -v 检查是否成功安装,若返回版本号则安装成功,若出现其他提示,请见 Windows 常见问题。

Windows 常见问题

Q. 双击运行下载成功后的构建工具闪退

A. 请不要更改文件名

Linux

Debian 分支 Linux 系统

  1. 打开终端,输入以下命令:

    sudo aot update

    sudo apt install cargo -y

  2. 若无报错,输入 cargo -V 检查是否输出版本号,若出现其他输出,请见 Linux 常见问题

其他系统待补充

Linux 常见问题

Q. 输入 cargo 时输出 bash: /usr/bin/cargo: No such file or directorycargo: command not found

A. 未成功安装 cargo,请检查安装完成后是否输出了其他信息。

Windows

准备阶段

  1. 确保系统安装了 cargo,可以在命令提示符(cmd)或者 PowerShell 使用 cargo -V 检查系统是否安装了 cargo。如果提示以下信息:
    • 'cargo' 不是内部或外部命令,也不是可运行的程序或批处理文件。
    • cargo : 无法将“cargo”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
    • 请点击 这里 按步骤安装构建工具
  2. 从 GitHub 下载源码到本地:
    • 若您安装了 git 工具,请使用 git clone https://github.com/TeamFlos/phira.git 将仓库克隆到本地。
    • 若您没有安装 git 工具,您也可以在 Phira 仓库页面点击 Code 按钮选择 Download ZIP 将代码下载到本地,随后将代码解压到本地任意目录。
    • 如果您无法连接到 GitHub,您也可以使用 git 加速网站提供的加速地址克隆与下载。
    • 若您要构建指定版本的 Phira,请前往 release 页面在 Assets 中选择下载 Source code(zip) 到本地,解压到任意路径即可。
    • 警告:为了防止玄学问题,我们不建议路径中包含除了 ASCII 编码包含字符以外的任何字符。
  3. perl,您可以在命令提示符(cmd)或者 PowerShell 使用 perl -v 检查系统是否安装了 perl,如果没有,请搜索并打开 MSYS2 UCRT64 输入 pacman -S perl 安装 perl
  4. 静态库文件,您可以 直接下载 或者在 缓存站 下载静态库文件,下载完成后直接解压到代码根目录下,如果提示覆盖文件,请点击覆盖。

开始构建

  1. 在命令提示符(cmd)或者 PowerShell 切换到代码根目录(如 D:\phira\
  2. 在命令提示符(cmd)或者 PowerShell 使用 cargo build -r --bin phira-main ,如果不出意外,在 openssl-sys(build) 时,您将卡很久很久,请不要退出,这是正常的。
  3. 构建完成后,在 .\target\release\ 目录下您可以找到编译完成的主程序
  4. 复制 .\assets\ 目录中的所有文件到 .\target\release\assets\ ,至此,构建流程全部完成,您可以直接运行 phira-main.exe 检查资源文件是否完整。
  • 注意:在此文档编写时,代码目录下的资源文件并不完整,如果您发现主程序闪退,您可以前往 release 页面下载任意版本,获取资源文件

常见问题

Q. 报错 failed to send request: 操作超时

A. 请检查网络环境,确保您可以正常访问 GitHub

Q. 报错 failed to send request: 无法解析服务器的名称或地址

A. 检查 DNS 或更换 DNS,更换后请刷新 DNS 缓存

Q. 构建过程中报错 error: failed to run custom build command for openssl-sys v0.9.99

A. 缺失 perl,请检查是否正确安装 perl 后再试

Q. 构建报错error occurred: Failed to find tool. Is gcc.exe installed? (see https://github.com/rust-lang/cc-rs#compile-time-requirements for help)

A. 请检查是否安装了 MSYS2 以及检查是否配置了环境变量

Q. 出现以下报错:

Error building OpenSSL dependencies:

Command: "make" "depend"

Failed to execute: program not found

A. 缺失 make 指令,请前往 MSYS2 终端中使用 pacman -S make 安装此命令。

Q. 报错包含 This perl inplementation doesn't produce lnix like paths

A. 使用的 perl 不适用于 gcc,请删除原有 perl 的环境变量或者直接卸载原有的 perl。

Q. 太麻烦了

A. 这样,直接去 release 页面下吧微软我真谢谢你

Linux

准备阶段

  1. 确保系统安装了 cargo,可以在终端使用 cargo -V 检查系统是否安装了 cargo,如果没有安装,请点击 这里按步骤安装构建工具

  2. 从 GitHub 下载源码到本地:

    • 若您是纯终端,建议使用 git 工具,请使用 git clone https://github.com/TeamFlos/phira.git 将仓库克隆到本地。
    • 若您使用了桌面环境,您也可以在 Phira 仓库页面点击 Code 按钮选择 Download ZIP 将代码下载到本地,随后将代码解压到本地任意目录。
    • 如果您无法连接到 GitHub,您也可以使用 git 加速网站提供的加速地址克隆与下载。
    • 若您要构建指定版本的 Phira,请前往 release 页面在 Assets 中选择下载 Source code(tar.gz) 到本地,解压到任意路径即可。
    • 警告:为了防止玄学问题,我们不建议路径中包含除了 ASCII 编码包含字符以外的任何字符。
  3. 静态库文件,您可以 直接下载 或者在 缓存站 下载静态库文件,下载完成后直接解压到代码根目录下,如果提示覆盖文件,请点击覆盖。

  4. 构建过程需要库文件补充,输入以下命令即可:

    sudo apt update

    sudo apt install libasound2-dev

    sudo apt install libgtk-3-dev

开始构建

  1. 打开终端,切换到代码根目录
  2. 输入 cargo build -r --bin phira-main ,直到编译完成。
  3. 复制 .\assets\ 目录中的所有文件到 .\target\release\assets\ ,至此,构建流程全部完成,您可以在带有桌面环境的情况下直接运行 phira-main 检查资源文件是否完整,若您没有桌面环境,程序将会闪退(实测 WSL 无法兼容,如果在 WSL 下运行将会闪退),至此,构建流程结束。
  • 注意:在此文档编写时,代码目录下的资源文件并不完整,如果您发现主程序闪退,您可以前往 release 页面下载任意版本,获取资源文件

常见问题

Q. 构建输出 failed to connect to GitHub

A. 请检查网络环境。

Q. 构建时报错 The pkg-config command could not be found

A. 缺失 pkg-config 指令,使用 sudo apt install pkg-config -y 安装即可。

Q. 构建报错 failed to run custom build command for alsa-sys v0.3.1

A. 缺失库文件

开发/运营过程中出现的意外事件

其实全都是 sjfhsjfh 的错

长风的柳絮

此事件发生于 2023-07-10 18:53 (此为 sjfhsjfh 在内部群发送消息 "我来磕大头了" 的时间)

事件经过

sjfhsjfh 在手动操作数据库(使用 SQL 语句)封禁用户 "长风的柳絮"时, 一不小心将未写完的 UPDATE 语句发送出去(没有加上 WHERE 关键字, 也没有写好 SET banned = TRUE), 导致所有用户的用户名被修改为 "长风的柳絮"

此事件耗费 Mivik 一整个下午和晚自习进行恢复(从日志中), 并未造成太多数据损失

后记

凡事都要两面看, 此事过后 Mivik 给审核组长提供了一个方便快捷的封禁按钮, 以防止类似事件再次发生. 不幸的是, sjfhsjfh 的扫帚星属性并未因此事件而消失, 见 v0.6.0 更新消息

v0.6.0 Update

此事件发生于 2024-01-01 23:16 (此为 Mivik 在上架讨论群内发送消息 "阿我草你干了啥?" 的时间)

长风的柳絮 事件类似, 为手动操作数据库造成的严重后果. 此事件也被称为 "长风的柳絮 2"

事件经过

sjfhsjfh 企图修改误放出的错误公告, 且心急如焚, 故不当操作导致所有用户的所有消息内容被修改为新的公告内容(即 v0.6.0 更新公告)

此事件耗费 Mivik 约一个晚上进行恢复, 当天的消息全部丢失

6thPecJam

此事件发生于 2024-02-10 凌晨约 00:40, 相较于预计的 PecJam 提交通道开启时间 2024-02-10 00:00, 已经推迟超多半个小时

注: 2024-02-09 为除夕, 大家都在守岁, 正好等着 PecJam 交稿

事件经过

sjfhsjfh 眼看大家在群里询问为何 PecJam 提交通道还未开启, 在焦急之中对 Phira 后端代码进行了胡乱修改, 提交时甚至没有进行编译检查, 最后不得不请 Mivik 从温暖的被窝中出来帮忙修复, 实在是罪大恶极